<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content=""><meta name="keywords" content=""><meta name="author" content="LiYang"><meta name="copyright" content="LiYang"><title>一条鲤鱼</title><link rel="shortcut icon" href="/melody-favicon.ico"><link rel="stylesheet" href="/css/index.css?version=1.9.0"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.9.0"><meta name="format-detection" content="telephone=no"><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="https://cdn.jsdelivr.net"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><script>var GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: undefined,
  copy: {
    success: 'Copy successfully',
    error: 'Copy error',
    noSupport: 'The browser does not support'
  },
  hexoVersion: '5.4.0'
} </script><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="一条鲤鱼" type="application/atom+xml">
</head><body><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="false"><div class="author-info"><div class="author-info__avatar text-center"><img src="/img/avatar.png"></div><div class="author-info__name text-center">LiYang</div><div class="author-info__description text-center"></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">Articles</span><span class="pull-right">13</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">Tags</span><span class="pull-right">6</span></a><a class="author-info-articles__categories article-meta" href="/categories"><span class="pull-left">Categories</span><span class="pull-right">7</span></a></div></div></div><nav class="no-bg" id="nav"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">一条鲤鱼</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus">   <a class="site-page" href="/">Home</a><a class="site-page" href="/archives">Archives</a><a class="site-page" href="/tags">Tags</a><a class="site-page" href="/categories">Categories</a></span><span class="pull-right"></span></div><div id="site-info"><div id="site-title">一条鲤鱼</div><div id="site-sub-title"></div></div></nav><div id="content-outer"><div class="layout" id="content-inner"><div class="recent-post-item article-container"><a class="article-title" href="/2022/11/07/gittest/">gittest</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2022-11-07</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E5%AE%9E%E7%94%A8%E6%8A%80%E6%9C%AF/">实用技术</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/%E6%8A%80%E6%9C%AF%E5%AD%A6%E4%B9%A0/">技术学习</a></span><div class="content"><h3 id="git简介"><a href="#git简介" class="headerlink" title="git简介"></a>git简介</h3><p>git是最先进的分布式版本控制系统</p>
<p><strong>集中式版本控制系统</strong>，版本库是集中存放在中央服务器的，而干活的时候，用的都是自己的电脑，所以要先从中央服务器取得最新的版本，然后开始干活，干完活了，再把自己的活推送给中央服务器。</p>
<p><strong>分布式版本控制系统</strong>与集中式版本控制系统有何不同呢？首先，分布式版本控制系统根本没有“中央服务器”，每个人的电脑上都是一个完整的版本库，这样，你工作的时候，就不需要联网了，因为版本库就在你自己的电脑上。</p>
<h3 id="安装git"><a href="#安装git" class="headerlink" title="安装git"></a>安装git</h3><h4 id="在Linux上安装Git"><a href="#在Linux上安装Git" class="headerlink" title="在Linux上安装Git"></a>在Linux上安装Git</h4><p>检查是否安装了git</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ git</span><br><span class="line">The program &#x27;git&#x27; is currently not installed. You can install it by typing:</span><br><span class="line">sudo apt-get install git</span><br></pre></td></tr></table></figure>

<p>安装git</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install git</span><br></pre></td></tr></table></figure>

<h4 id="在windows上安装git"><a href="#在windows上安装git" class="headerlink" title="在windows上安装git"></a>在windows上安装git</h4><p>在Windows上使用Git，可以从Git官网直接<a target="_blank" rel="noopener" href="https://git-scm.com/downloads">下载安装程序</a>，然后按默认选项安装即可。</p>
<p>安装完成后，在开始菜单里找到“Git”-&gt;“Git Bash”，蹦出一个类似命令行窗口的东西，就说明Git安装成功！</p>
<p>安装完成后，还需要最后一步设置，在命令行输入：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global user.name &quot;Liyang&quot;</span><br><span class="line">$ git config --global user.email &quot;liyang199802@google.com&quot;</span><br></pre></td></tr></table></figure>

<p>注意<code>git config</code>命令的<code>--global</code>参数，用了这个参数，表示你这台机器上所有的Git仓库都会使用这个配置，当然也可以对某个仓库指定不同的用户名和Email地址。</p>
<h3 id="创建版本库"><a href="#创建版本库" class="headerlink" title="创建版本库"></a>创建版本库</h3><p>版本库又名仓库，英文名<strong>repository</strong>，可以简单理解成一个目录，这个目录里面的所有文件都可以被Git管理起来，每个文件的修改、删除，Git都能跟踪，以便任何时刻都可以追踪历史，或者在将来某个时刻可以“还原”。</p>
<ul>
<li>第一步</li>
</ul>
<p>选择一个合适的地方，创建一个空目录：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ mkdir learngit</span><br><span class="line">$ cd learngit</span><br><span class="line">$ pwd</span><br><span class="line">/Users/michael/learngit</span><br></pre></td></tr></table></figure>

<p><code>pwd</code>命令用于显示当前目录。在他的Mac上，这个仓库位于<code>/Users/michael/learngit</code>。</p>
<p>Windows系统要确保目录名（包括父目录）不包含中文</p>
<ul>
<li>第二步</li>
</ul>
<p>通过<code>git init</code>命令把这个目录变成Git可以管理的仓库：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ git init</span><br><span class="line">Initialized empty Git repository in /Users/michael/learngit/.git/</span><br></pre></td></tr></table></figure>

<p><img src="/2022/11/07/gittest/capture_20210205102435645.bmp" alt="capture_20210205102435645"></p>
<h4 id="把文件添加到版本库"><a href="#把文件添加到版本库" class="headerlink" title="把文件添加到版本库"></a>把文件添加到版本库</h4><p>所有的版本控制系统，其实只能跟踪文本文件的改动，比如TXT文件，网页，所有的程序代码等等，Git也不例外。</p>
<p>Microsoft的Word格式是二进制格式，因此，版本控制系统是没法跟踪Word文件的改动的。</p>
<p>强烈建议使用标准的UTF-8编码，所有语言使用同一种编码，既没有冲突，又被所有平台所支持。</p>
<p>千万不要使用Windows自带的<strong>记事本</strong>编辑任何文本文件。原因是Microsoft开发记事本的团队使用了一个非常弱智的行为来保存UTF-8编码的文件，他们自作聪明地在每个文件开头添加了0xefbbbf（十六进制）的字符，你会遇到很多不可思议的问题。</p>
<p>建议下载<a target="_blank" rel="noopener" href="http://notepad-plus-plus.org/">Notepad++</a>代替记事本，不但功能强大，而且免费！记得把Notepad++的默认编码设置为UTF-8 without BOM即可。</p>
<p>首先在git仓库目录下创建一个<code>readme.txt</code>文件</p>
<p>第一步，用命令<code>git add</code>告诉Git，把文件添加到仓库：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git add readme.txt</span><br></pre></td></tr></table></figure>

<p>第二步，用命令<code>git commit</code>告诉Git，把文件提交到仓库：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git commit -m &quot;wrote a readme file&quot;</span><br></pre></td></tr></table></figure>

<p><code>git commit</code>命令执行成功后会告诉你，<code>1 file changed</code>：1个文件被改动（我们新添加的readme.txt文件）；<code>2 insertions</code>：插入了两行内容（readme.txt有两行内容）。</p>
<p><img src="/2022/11/07/gittest/capture_20210205104533081.bmp" alt="capture_20210205104533081"></p>
<h3 id="修改版本库文件"><a href="#修改版本库文件" class="headerlink" title="修改版本库文件"></a>修改版本库文件</h3><p>将readme.txt文件修改。</p>
<p><code>git status</code>命令查看结果：</p>
<p><img src="/2022/11/07/gittest/capture_20210205104956360.bmp" alt="capture_20210205104956360"></p>
<p><code>git status</code>命令可以让我们时刻掌握仓库当前的状态，上面的命令输出告诉我们，<code>readme.txt</code>被修改过了，但还没有准备提交的修改。</p>
<p><code>git diff</code>命令查看修改的具体内容：</p>
<p><img src="/2022/11/07/gittest/capture_20210205105250030.bmp" alt="capture_20210205105250030"></p>
<p><code>git diff</code>顾名思义就是查看difference，显示的格式正是Unix通用的diff格式。</p>
<p>提交修改和提交新文件是一样的两步，第一步是<code>git add</code> 第二步是<code>git commit</code> 。提交后，我们再用<code>git status</code>命令看看仓库的当前状态：</p>
<p><img src="/2022/11/07/gittest/capture_20210205105636275.bmp" alt="capture_20210205105636275"></p>
<p>Git告诉我们当前没有需要提交的修改，而且，工作目录是干净（working tree clean）的。</p>
<h3 id="版本回退"><a href="#版本回退" class="headerlink" title="版本回退"></a>版本回退</h3><p><code>git log</code>命令显示从最近到最远的提交日志</p>
<p>如果嫌输出信息太多，看得眼花缭乱的，可以试试加上<code>--pretty=oneline</code>参数。</p>
<p><img src="/2022/11/07/gittest/capture_20210205110017014.bmp" alt="capture_20210205110017014"></p>
<p><code>git reset</code>命令回退到某个版本</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">git reset --hard HEAD^</span><br><span class="line">回退到上一个版本</span><br><span class="line">cat readme.txt</span><br><span class="line">查看是否回退到上一版本</span><br><span class="line">git log --pretty=oneline</span><br><span class="line">查看当前日志，发现最新版本已经找不到了</span><br><span class="line">git reset --hard 4272d</span><br><span class="line">根据日志中的版本号（SHA1序列号）返回版本</span><br></pre></td></tr></table></figure>

<p><img src="/2022/11/07/gittest/capture_20210205110912616.bmp" alt="capture_20210205110912616"></p>
<p>如果关掉了git，找不到版本号，可以根据<code>git reflog</code> 来查看所有的版本号：</p>
<p><img src="/2022/11/07/gittest/capture_20210205111150806.bmp" alt="capture_20210205111150806"></p>
<h3 id="工作区和暂存区"><a href="#工作区和暂存区" class="headerlink" title="工作区和暂存区"></a>工作区和暂存区</h3><h4 id="工作区（Working-Directory）"><a href="#工作区（Working-Directory）" class="headerlink" title="工作区（Working Directory）"></a>工作区（Working Directory）</h4><p>就是你在电脑里能看到的目录，比如<code>Gitrepository</code> 文件夹就是一个工作区。</p>
<h4 id="版本库（Repository）"><a href="#版本库（Repository）" class="headerlink" title="版本库（Repository）"></a>版本库（Repository）</h4><p>工作区有一个隐藏目录<code>.git</code>，这个不算工作区，而是Git的版本库。</p>
<p>Git的版本库里存了很多东西，其中最重要的就是称为stage（或者叫index）的暂存区，还有Git为我们自动创建的第一个分支<code>master</code>，以及指向<code>master</code>的一个指针叫<code>HEAD</code>。 </p>
<p><img src="/2022/11/07/gittest/0.jpg" alt="0"></p>
<p>前面讲了我们把文件往Git版本库里添加的时候，是分两步执行的：</p>
<p>第一步是用<code>git add</code>把文件添加进去，实际上就是把文件修改添加到暂存区；</p>
<p>第二步是用<code>git commit</code>提交更改，实际上就是把暂存区的所有内容提交到当前分支。</p>
<p>因为我们创建Git版本库时，Git自动为我们创建了唯一一个<code>master</code>分支，所以，现在，<code>git commit</code>就是往<code>master</code>分支上提交更改。</p>
<p>你可以简单理解为，需要提交的文件修改通通放到暂存区，然后，一次性提交暂存区的所有修改。</p>
<p><code>git diff HEAD -- readme.txt</code>命令可以查看工作区和版本库里面最新版本的区别。</p>
<h3 id="撤销修改"><a href="#撤销修改" class="headerlink" title="撤销修改"></a>撤销修改</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -- readme.txt</span><br></pre></td></tr></table></figure>

<h4 id="未add的文件（工作区）撤销修改"><a href="#未add的文件（工作区）撤销修改" class="headerlink" title="未add的文件（工作区）撤销修改"></a>未add的文件（工作区）撤销修改</h4><p>命令<code>git checkout -- readme.txt</code>意思就是，把<code>readme.txt</code>文件在工作区的修改全部撤销，这里有两种情况：</p>
<p>一种是<code>readme.txt</code>自修改后还没有被放到暂存区，现在，撤销修改就回到和版本库一模一样的状态；</p>
<p>一种是<code>readme.txt</code>已经添加到暂存区后，又作了修改，现在，撤销修改就回到添加到暂存区后的状态。</p>
<p>总之，就是让这个文件回到最近一次<code>git commit</code>或<code>git add</code>时的状态。</p>
<h4 id="已add的文件（暂存区）撤销修改"><a href="#已add的文件（暂存区）撤销修改" class="headerlink" title="已add的文件（暂存区）撤销修改"></a>已add的文件（暂存区）撤销修改</h4><p>命令<code>git reset HEAD &lt;file&gt;</code>可以把暂存区的修改撤销掉（unstage），重新放回工作区。</p>
<p><code>git reset</code>命令既可以回退版本，也可以把暂存区的修改回退到工作区。当我们用<code>HEAD</code>时，表示最新的版本。</p>
<h3 id="删除文件"><a href="#删除文件" class="headerlink" title="删除文件"></a>删除文件</h3><p>通常直接在文件管理器中把没用的文件删了，或者用<code>rm</code>命令删了：<code>$ rm test.txt</code></p>
<p>这个时候，Git知道你删除了文件，因此，工作区和版本库就不一致了，<code>git status</code>命令会立刻告诉你哪些文件被删除了。 </p>
<p><img src="/2022/11/07/gittest/capture_20210206234618063.bmp" alt="capture_20210206234618063"></p>
<hr>
<p>现在你有两个选择，一是确实要从版本库中删除该文件，那就用命令<code>git rm</code>删掉，并且<code>git commit</code>：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ git rm test.txt</span><br><span class="line">rm &#x27;test.txt&#x27;</span><br><span class="line"></span><br><span class="line">$ git commit -m &quot;remove test.txt&quot;</span><br><span class="line">[master d46f35e] remove test.txt</span><br><span class="line"> 1 file changed, 1 deletion(-)</span><br><span class="line"> delete mode 100644 test.txt</span><br></pre></td></tr></table></figure>

<p>现在，文件就从版本库中被删除了。</p>
<hr>
<p>另一种情况是删错了，因为版本库里还有呢，所以可以很轻松地把误删的文件恢复到最新版本：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -- test.txt</span><br></pre></td></tr></table></figure>

<p><code>git checkout</code>其实是用版本库里的版本替换工作区的版本，无论工作区是修改还是删除，都可以“一键还原”。</p>
<h3 id="远程仓库"><a href="#远程仓库" class="headerlink" title="远程仓库"></a>远程仓库</h3><p>注册github账号：LiYang-gfkd    liyang199802.com    ly490415836。</p>
<p>本地Git仓库和GitHub仓库之间的传输是通过SSH加密的，所以，需要一点设置：</p>
<p><strong>第1步</strong>：创建SSH Key。在用户主目录下，看看有没有.ssh目录，如果有，再看看这个目录下有没有<code>id_rsa</code>和<code>id_rsa.pub</code>这两个文件，如果已经有了，可直接跳到下一步。如果没有，打开Shell（Windows下打开Git Bash），创建SSH Key：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-keygen -t rsa -C &quot;liyang199802@gmail.com&quot;</span><br></pre></td></tr></table></figure>

<p>然后一路回车，使用默认值即可，由于这个Key也不是用于军事目的，所以也无需设置密码。</p>
<p>如果一切顺利的话，可以在用户主目录里找到<code>.ssh</code>目录，里面有<code>id_rsa</code>和<code>id_rsa.pub</code>两个文件，这两个就是SSH Key的秘钥对，<code>id_rsa</code>是私钥，不能泄露出去，<code>id_rsa.pub</code>是公钥，可以放心地告诉任何人。</p>
<p><strong>第2步</strong>：登陆GitHub，打开“Account settings”，“SSH Keys”页面：</p>
<p>然后，点“Add SSH Key”，填上任意Title，在Key文本框里粘贴<code>id_rsa.pub</code>文件的内容,点“Add Key”，你就应该看到已经添加的Key。</p>
<p><strong>为什么GitHub需要SSH Key呢？</strong>因为GitHub需要识别出你推送的提交确实是你推送的，而不是别人冒充的，而Git支持SSH协议，所以，GitHub只要知道了你的公钥，就可以确认只有你自己才能推送。</p>
<p>当然，GitHub允许你添加多个Key。假定你有若干电脑，你一会儿在公司提交，一会儿在家里提交，只要把每台电脑的Key都添加到GitHub，就可以在每台电脑上往GitHub推送了。</p>
<p>最后友情提示，在GitHub上免费托管的Git仓库，任何人都可以看到喔（但只有你自己才能改）。所以，不要把敏感信息放进去。</p>
<h3 id="添加远程库"><a href="#添加远程库" class="headerlink" title="添加远程库"></a>添加远程库</h3><p><strong>现在的情景是，你已经在本地创建了一个Git仓库后，又想在GitHub创建一个Git仓库，并且让这两个仓库进行远程同步，这样，GitHub上的仓库既可以作为备份，又可以让其他人通过该仓库来协作，一举多得。</strong></p>
<p><strong>第一步</strong>，</p>
<p>登陆GitHub，然后，在右上角找到“Create a new repo”按钮，创建一个新的仓库：</p>
<p><img src="/2022/11/07/gittest/capture_20210207165857448.bmp" alt="capture_20210207165857448"></p>
<p>目前，在GitHub上的这个<code>gitrepository</code>仓库还是空的，GitHub告诉我们，可以从这个仓库克隆出新的仓库，也可以把一个已有的本地仓库与之关联，然后，把本地仓库的内容推送到GitHub仓库。</p>
<p><img src="/2022/11/07/gittest/capture_20210207171326284.bmp" alt="capture_20210207171326284"></p>
<p><strong>第二步，</strong></p>
<p>现在，我们根据GitHub的提示，在本地的<code>gitrepository</code>仓库下运行命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ git remote add origin git@github.com:yourgithubname/gitrepository.git</span><br><span class="line">或</span><br><span class="line">$ git remote add origin https://github.com/LiYang-gfkd/gitrepository.git</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>请千万注意，把上面的<code>yourgithubname</code>替换成你自己的GitHub账户名，否则，你在本地关联的就是别人的远程库，关联没有问题，但是你以后推送是推不上去的，因为你的SSH Key公钥不在别人的账户列表中。</p>
<p>添加后，远程库的名字就是<code>origin</code>，这是Git默认的叫法，也可以改成别的，但是<code>origin</code>这个名字一看就知道是远程库。</p>
<p><strong>第三步</strong></p>
<p>把本地库的所有内容推送到远程库上：</p>
<p><img src="/2022/11/07/gittest/capture_20210207172015808.bmp" alt="capture_20210207172015808"></p>
<p>把本地库的内容推送到远程，用<code>git push</code>命令，实际上是把当前分支<code>master</code>推送到远程。</p>
<p>由于远程库是空的，我们第一次推送<code>master</code>分支时，加上了<code>-u</code>参数，Git不但会把本地的<code>master</code>分支内容推送的远程新的<code>master</code>分支，还会把本地的<code>master</code>分支和远程的<code>master</code>分支关联起来，在以后的推送或者拉取时就可以简化命令。</p>
<p><strong>结果</strong></p>
<p>推送成功后，可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样：</p>
<p><img src="/2022/11/07/gittest/image-20210207173356128.png" alt="image-20210207173356128"></p>
<p>从现在起，只要本地作了提交，就可以通过命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git push origin master</span><br></pre></td></tr></table></figure>

<p>把本地<code>master</code>分支的最新修改推送至GitHub，现在，你就拥有了真正的分布式版本库！</p>
<p><img src="/2022/11/07/gittest/image-20210207174133462.png" alt="image-20210207174133462"></p>
<h4 id="SSH警告"><a href="#SSH警告" class="headerlink" title="SSH警告"></a>SSH警告</h4><p>当你第一次使用Git的<code>clone</code>或者<code>push</code>命令连接GitHub时，会得到一个警告：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">The authenticity of host &#x27;github.com (xx.xx.xx.xx)&#x27; can&#x27;t be established.</span><br><span class="line">RSA key fingerprint is xx.xx.xx.xx.xx.</span><br><span class="line">Are you sure you want to continue connecting (yes/no)?</span><br></pre></td></tr></table></figure>

<p>这是因为Git使用SSH连接，而SSH连接在第一次验证GitHub服务器的Key时，需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器，输入<code>yes</code>回车即可。</p>
<p>Git会输出一个警告，告诉你已经把GitHub的Key添加到本机的一个信任列表里了：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Warning: Permanently added &#x27;github.com&#x27; (RSA) to the list of known hosts.</span><br></pre></td></tr></table></figure>

<p>这个警告只会出现一次，后面的操作就不会有任何警告了。</p>
<p>如果你实在担心有人冒充GitHub服务器，输入<code>yes</code>前可以对照<a target="_blank" rel="noopener" href="https://help.github.com/articles/what-are-github-s-ssh-key-fingerprints/">GitHub的RSA Key的指纹信息</a>是否与SSH连接给出的一致。</p>
<h3 id="从远程库克隆"><a href="#从远程库克隆" class="headerlink" title="从远程库克隆"></a>从远程库克隆</h3><p>假设我们从零开发，那么最好的方式是先创建远程库，然后，从远程库克隆。</p>
<p>首先，登陆GitHub，创建一个新的仓库，名字叫<code>gitskills</code>：</p>
<p><img src="/2022/11/07/gittest/image-20210207213706862.png" alt="image-20210207213706862"></p>
<p>我们勾选<code>Initialize this repository with a README</code>，这样GitHub会自动为我们创建一个<code>README.md</code>文件。创建完毕后，可以看到<code>README.md</code>文件。</p>
<p>现在，远程库已经准备好了，下一步是用命令<code>git clone</code>克隆一个本地库：</p>
<p>进入<code>gitskills</code>目录看看，已经有<code>README.md</code>文件了：</p>
<p><img src="/2022/11/07/gittest/image-20210207214822968.png" alt="image-20210207214822968"></p>
<h4 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h4><p>要克隆一个仓库，首先必须知道仓库的地址，然后使用<code>git clone</code>命令克隆。</p>
<p>Git支持多种协议，包括<code>https</code>，但<code>ssh</code>协议速度最快。</p>
<h3 id="分支管理"><a href="#分支管理" class="headerlink" title="分支管理"></a>分支管理</h3><p>分支在实际中有什么用呢？假设你准备开发一个新功能，但是需要两周才能完成，第一周你写了50%的代码，如果立刻提交，由于代码还没写完，不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交，又存在丢失每天进度的巨大风险。</p>
<p>现在有了分支，就不用怕了。你创建了一个属于你自己的分支，别人看不到，还继续在原来的分支上正常工作，而你在自己的分支上干活，想提交就提交，直到开发完毕后，再一次性合并到原来的分支上，这样，既安全，又不影响别人工作。</p>
<h3 id="创建与合并分支"><a href="#创建与合并分支" class="headerlink" title="创建与合并分支"></a>创建与合并分支</h3><h4 id="理论讲解"><a href="#理论讲解" class="headerlink" title="理论讲解"></a>理论讲解</h4><p>在<a target="_blank" rel="noopener" href="https://www.liaoxuefeng.com/wiki/896043488029600/897013573512192">版本回退</a>里，你已经知道，每次提交，Git都把它们串成一条时间线，这条时间线就是一个分支。截止到目前，只有一条时间线，在Git里，这个分支叫主分支，即<code>master</code>分支。<code>HEAD</code>严格来说不是指向提交，而是指向<code>master</code>，<code>master</code>才是指向提交的，所以，<code>HEAD</code>指向的就是当前分支。</p>
<p>一开始的时候，<code>master</code>分支是一条线，Git用<code>master</code>指向最新的提交，再用<code>HEAD</code>指向<code>master</code>，就能确定当前分支，以及当前分支的提交点：</p>
<p><img src="/2022/11/07/gittest/0.png" alt="0"></p>
<p>每次提交，<code>master</code>分支都会向前移动一步，这样，随着你不断提交，<code>master</code>分支的线也越来越长。</p>
<p>当我们创建新的分支，例如<code>dev</code>时，Git新建了一个指针叫<code>dev</code>，指向<code>master</code>相同的提交，再把<code>HEAD</code>指向<code>dev</code>，就表示当前分支在<code>dev</code>上：</p>
<p><img src="/2022/11/07/gittest/l.png" alt="l"></p>
<p>你看，Git创建一个分支很快，因为除了增加一个<code>dev</code>指针，改改<code>HEAD</code>的指向，工作区的文件都没有任何变化！</p>
<p>不过，从现在开始，对工作区的修改和提交就是针对<code>dev</code>分支了，比如新提交一次后，<code>dev</code>指针往前移动一步，而<code>master</code>指针不变：</p>
<p>![l (1)](gittest/l (1).png)</p>
<p>假如我们在<code>dev</code>上的工作完成了，就可以把<code>dev</code>合并到<code>master</code>上。Git怎么合并呢？最简单的方法，就是直接把<code>master</code>指向<code>dev</code>的当前提交，就完成了合并：</p>
<p>![0 (1)](gittest/0 (1).png)</p>
<p>所以Git合并分支也很快！就改改指针，工作区内容也不变！</p>
<p>合并完分支后，甚至可以删除<code>dev</code>分支。删除<code>dev</code>分支就是把<code>dev</code>指针给删掉，删掉后，我们就剩下了一条<code>master</code>分支：</p>
<p>![0 (2)](gittest/0 (2).png)</p>
<h4 id="实战演练"><a href="#实战演练" class="headerlink" title="实战演练"></a>实战演练</h4><p><strong>第一步</strong></p>
<p>创建<code>dev</code>分支，然后切换到<code>dev</code>分支：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout -b dev</span><br><span class="line">Switched to a new branch &#x27;dev&#x27;</span><br></pre></td></tr></table></figure>

<p><code>git checkout</code>命令加上<code>-b</code>参数表示创建并切换，相当于以下两条命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ git branch dev</span><br><span class="line">$ git checkout dev</span><br><span class="line">Switched to branch &#x27;dev&#x27;</span><br></pre></td></tr></table></figure>

<p><strong>第二步</strong></p>
<p>用<code>git branch</code>命令查看当前分支：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ git branch</span><br><span class="line">* dev</span><br><span class="line">  master</span><br></pre></td></tr></table></figure>

<p><code>git branch</code>命令会列出所有分支，当前分支前面会标一个<code>*</code>号。</p>
<p>然后，我们就可以在<code>dev</code>分支上正常提交，比如对<code>readme.txt</code>做个修改，加上一行：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Creating a new branch is quick.</span><br></pre></td></tr></table></figure>

<p>然后提交：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ git add readme.txt </span><br><span class="line">$ git commit -m &quot;branch test&quot;</span><br><span class="line">[dev b17d20e] branch test</span><br><span class="line"> 1 file changed, 1 insertion(+)</span><br></pre></td></tr></table></figure>

<p>现在，<code>dev</code>分支的工作完成，我们就可以切换回<code>master</code>分支：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ git checkout master</span><br><span class="line">Switched to branch &#x27;master&#x27;</span><br></pre></td></tr></table></figure>

<p>切换回<code>master</code>分支后，再查看一个<code>readme.txt</code>文件，刚才添加的内容不见了！因为那个提交是在<code>dev</code>分支上，而<code>master</code>分支此刻的提交点并没有变：</p>
<p>![0 (3)](gittest/0 (3).png)</p>
<p>现在，我们把<code>dev</code>分支的工作成果合并到<code>master</code>分支上：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ git merge dev</span><br><span class="line">Updating d46f35e..b17d20e</span><br><span class="line">Fast-forward</span><br><span class="line"> readme.txt | 1 +</span><br><span class="line"> 1 file changed, 1 insertion(+)</span><br></pre></td></tr></table></figure>

<p><code>git merge</code>命令用于合并指定分支到当前分支。合并后，再查看<code>readme.txt</code>的内容，就可以看到，和<code>dev</code>分支的最新提交是完全一样的。</p>
<p>注意到上面的<code>Fast-forward</code>信息，Git告诉我们，这次合并是“快进模式”，也就是直接把<code>master</code>指向<code>dev</code>的当前提交，所以合并速度非常快。</p>
<p>当然，也不是每次合并都能<code>Fast-forward</code>，我们后面会讲其他方式的合并。</p>
<p>合并完成后，就可以放心地删除<code>dev</code>分支了：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ git branch -d dev</span><br><span class="line">Deleted branch dev (was b17d20e).</span><br></pre></td></tr></table></figure>

<p>删除后，查看<code>branch</code>，就只剩下<code>master</code>分支了：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ git branch</span><br><span class="line">* master</span><br></pre></td></tr></table></figure>

<p>因为创建、合并和删除分支非常快，所以Git鼓励你使用分支完成某个任务，合并后再删掉分支，这和直接在<code>master</code>分支上工作效果是一样的，但过程更安全。</p>
<h4 id="switch"><a href="#switch" class="headerlink" title="switch"></a><strong>switch</strong></h4><p>我们注意到切换分支使用<code>git checkout &lt;branch&gt;</code>，而前面讲过的撤销修改则是<code>git checkout -- &lt;file&gt;</code>，同一个命令，有两种作用，确实有点令人迷惑。</p>
<p>实际上，切换分支这个动作，用<code>switch</code>更科学。因此，最新版本的Git提供了新的<code>git switch</code>命令来切换分支：</p>
<p>创建并切换到新的<code>dev</code>分支，可以使用：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git switch -c dev</span><br></pre></td></tr></table></figure>

<p>直接切换到已有的<code>master</code>分支，可以使用：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git switch master</span><br></pre></td></tr></table></figure>

<p>使用新的<code>git switch</code>命令，比<code>git checkout</code>要更容易理解。</p>
<h4 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h4><p>Git鼓励大量使用分支：</p>
<p>查看分支：<code>git branch</code></p>
<p>创建分支：<code>git branch &lt;name&gt;</code></p>
<p>切换分支：<code>git checkout &lt;name&gt;</code>或者<code>git switch &lt;name&gt;</code></p>
<p>创建+切换分支：<code>git checkout -b &lt;name&gt;</code>或者<code>git switch -c &lt;name&gt;</code></p>
<p>合并某分支到当前分支：<code>git merge &lt;name&gt;</code></p>
<p>删除分支：<code>git branch -d &lt;name&gt;</code></p>
<hr>
<h3 id="解决冲突"><a href="#解决冲突" class="headerlink" title="解决冲突"></a>解决冲突</h3><p>准备新的<code>feature1</code>分支，继续我们的新分支开发，</p>
<p>修改<code>readme.txt</code>最后一行，改为：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Creating a new branch is quick AND simple.</span><br></pre></td></tr></table></figure>

<p>在<code>feature1</code>分支上提交</p>
<p>切换到<code>master</code>分支</p>
<p>Git还会自动提示我们当前<code>master</code>分支比远程的<code>master</code>分支要超前1个提交。</p>
<p><img src="/2022/11/07/gittest/capture_20210301124826725.bmp" alt="capture_20210301124826725"></p>
<p>在<code>master</code>分支上把<code>readme.txt</code>文件的最后一行改为：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Creating a new branch is quick &amp; simple.</span><br></pre></td></tr></table></figure>

<p><img src="/2022/11/07/gittest/image-20210301125239468.png" alt="image-20210301125239468"></p>
<p>现在，<code>master</code>分支和<code>feature1</code>分支各自都分别有新的提交，变成了这样：</p>
<p>![0 (4)](gittest/0 (4).png)</p>
<p>这种情况下，Git无法执行“快速合并”，只能试图把各自的修改合并起来，但这种合并就可能会有冲突，我们试试看：</p>
<p><img src="/2022/11/07/gittest/image-20210301125453009.png" alt="image-20210301125453009"></p>
<p>果然冲突了！Git告诉我们，<code>readme.txt</code>文件存在冲突，必须手动解决冲突后再提交。<code>git status</code>也可以告诉我们冲突的文件：</p>
<p><img src="/2022/11/07/gittest/image-20210301125557757.png" alt="image-20210301125557757"></p>
<p>查看readme.txt的内容：</p>
<p><img src="/2022/11/07/gittest/image-20210301125639260.png" alt="image-20210301125639260"></p>
<p>Git用<code>&lt;&lt;&lt;&lt;&lt;&lt;&lt;</code>，<code>=======</code>，<code>&gt;&gt;&gt;&gt;&gt;&gt;&gt;</code>标记出不同分支的内容，我们修改如下后保存：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Creating a new branch is quick and simple.</span><br></pre></td></tr></table></figure>

<p>再提交：</p>
<p><img src="/2022/11/07/gittest/image-20210301125934367.png" alt="image-20210301125934367"></p>
<p>现在，<code>master</code>分支和<code>feature1</code>分支变成了下图所示：</p>
<p>![0 (5)](gittest/0 (5).png)</p>
<p>用带参数的<code>git log</code>也可以看到分支的合并情况，最后，删除featurel分支：</p>
<p><img src="/2022/11/07/gittest/image-20210301130525818.png" alt="image-20210301130525818"></p>
<p><strong>小结</strong></p>
<p>当Git无法自动合并分支时，就必须首先解决冲突。解决冲突后，再提交，合并完成。</p>
<p>解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容，再提交。</p>
<p>用<code>git log --graph</code>命令可以看到分支合并图。</p>
<h3 id="使用gitee"><a href="#使用gitee" class="headerlink" title="使用gitee"></a>使用gitee</h3><p>和GitHub相比，Gitee也提供免费的Git仓库。此外，还集成了代码质量检测、项目演示等功能。对于团队协作开发，Gitee还提供了项目管理、代码托管、文档管理的服务，5人以下小团队免费。</p>
<p><strong>第一步</strong> 上传公钥</p>
<p>我们在Gitee上注册账号并登录后，需要先上传自己的SSH公钥。选择右上角用户头像 -&gt; 菜单“修改资料”，然后选择“SSH公钥”，填写一个便于识别的标题，然后把用户主目录下的<code>.ssh/id_rsa.pub</code>文件的内容粘贴进去：</p>
<p><img src="/2022/11/07/gittest/image-20210226231913516.png" alt="image-20210226231913516"></p>
<p><strong>第二步</strong> 本地git仓库关联到远程gitee库</p>
<p>首先，我们在Gitee上创建一个新的项目，选择右上角用户头像 -&gt; 菜单“控制面板”，然后点击“创建项目”：</p>
<p><img src="/2022/11/07/gittest/image-20210226232420988.png" alt="image-20210226232420988"></p>
<p>项目名称最好与本地库保持一致：</p>
<p>然后，我们在本地库上使用命令<code>git remote add</code>把它和Gitee的远程库关联：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote add origin git@gitee.com:liaoxuefeng/learngit.git</span><br></pre></td></tr></table></figure>

<p>之后，就可以正常地用<code>git push</code>和<code>git pull</code>推送了！如果在使用命令<code>git remote add</code>时报错：</p>
<p><img src="/2022/11/07/gittest/image-20210226232918147.png" alt="image-20210226232918147"></p>
<p>这说明本地库已经关联了一个名叫<code>origin</code>的远程库，此时，可以先用<code>git remote -v</code>查看远程库信息：</p>
<p>可以看到，本地库已经关联了<code>origin</code>的远程库，并且，该远程库指向GitHub。</p>
<p>我们可以删除已有的GitHub远程库，再关联Gitee的远程库（注意路径中需要填写正确的用户名），我们再查看远程库信息：</p>
<p><img src="/2022/11/07/gittest/image-20210226233141952.png" alt="image-20210226233141952"></p>
<p>现在可以看到，origin已经被关联到Gitee的远程库了。通过<code>git push</code>命令就可以把本地库推送到Gitee上。</p>
<p><strong>第三步</strong> 同时关联gitee和github</p>
<p>git本身是分布式版本控制系统，可以同步到另外一个远程库，当然也可以同步到另外两个远程库。</p>
<p>使用多个远程库时，我们要注意，git给远程库起的默认名称是<code>origin</code>，如果有多个远程库，我们需要用不同的名称来标识不同的远程库。</p>
<p>仍然以<code>learngit</code>本地库为例，我们先删除已关联的名为<code>origin</code>的远程库，然后，先关联GitHub的远程库，远程库的名称叫<code>github</code>，接着，再关联Gitee的远程库，远程库的名称叫<code>gitee</code>，</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git remote add github https://github.com/LiYang-gfkd/gitrepository.git</span><br><span class="line">git remote add gitee https://gitee.com/liyang199802/gitrepository.git</span><br></pre></td></tr></table></figure>

<p>用<code>git remote -v</code>查看远程库信息，可以看到两个远程库：</p>
<p><img src="/2022/11/07/gittest/image-20210226233741889.png" alt="image-20210226233741889"></p>
<p>如果要推送到GitHub，使用命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push github master</span><br></pre></td></tr></table></figure>

<p>如果要推送到Gitee，使用命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push gitee master</span><br></pre></td></tr></table></figure>

<p>这样一来，我们的本地库就可以同时与多个远程库互相同步：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">┌─────────┐ ┌─────────┐</span><br><span class="line">│ GitHub  │ │  Gitee  │</span><br><span class="line">└─────────┘ └─────────┘</span><br><span class="line">     ▲           ▲</span><br><span class="line">     └─────┬─────┘</span><br><span class="line">           │</span><br><span class="line">    ┌─────────────┐</span><br><span class="line">    │ Local Repo  │</span><br><span class="line">    └─────────────┘</span><br></pre></td></tr></table></figure>

<p>Gitee也同样提供了Pull request功能，可以让其他小伙伴参与到开源项目中来。</p>
<p><strong>第四步</strong> 将本地库push到gitee上</p>
<p><img src="/2022/11/07/gittest/image-20210226234051164.png" alt="image-20210226234051164"></p>
<hr>
<h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a><strong>遇到的问题</strong></h2><h3 id="1：解决error：failed-to-push-some-refs-to-‘xxxx’"><a href="#1：解决error：failed-to-push-some-refs-to-‘xxxx’" class="headerlink" title="1：解决error：failed to push some refs to ‘xxxx’"></a>1：解决error：failed to push some refs to ‘xxxx’</h3><ul>
<li>用 <code>git push origin master</code> 向远程仓库推送的时候报如下错误：</li>
</ul>
<p><code>error: failed to push some refs to &#39;https://github.com/ZJyoung1997/JZShop.git&#39; hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., &#39;git pull ...&#39;) before pushing again. hint: See the &#39;Note about fast-forwards&#39; in &#39;git push --help&#39; for details.</code></p>
<ul>
<li>原因是远程仓库中的文件和我们本地的仓库有差异，例如你的远程仓库有个文件Readme. md，但是本地仓库却没有，就可能会出现这种情况。 我的就是远程仓库中有Readme. md文件，而本地仓库中没有该文件造成的。还有就是因为平时在协会中，用协会电脑开发，回到寝室后又会用自己的电脑开发，这可能也是导致这种问题的原因。这种情况有解决办法，就是将本地仓库直接干掉，然后重新从远程clone一个，但是这样显然太麻烦了，可以用合并分支的方法。</li>
<li>解决办法：</li>
</ul>
<p><code>git pull --rebase origin master</code></p>
<p>然后再push</p>
<p>git pull命令用于从另一个存储库或本地分支获取并集成(整合)，取回远程主机某个分支的更新，再与本地的指定分支合并。</p>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/">如何理解“进入内核态”?</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2022-06-20</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/">操作系统</a><i class="fa fa-angle-right" aria-hidden="true"></i><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/Linux/">Linux</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/Linux/">Linux</a></span><div class="content"><h1 id="如何理解「进入内核态」？"><a href="#如何理解「进入内核态」？" class="headerlink" title="如何理解「进入内核态」？"></a>如何理解「进入内核态」？</h1><p>这是个需要很多知识储备才能解答的问题，比如需要了解内存的分段，分页，中断，特权级等机制，信息量比较大。</p>
<p>本文的主要内容包括如下几个部分：</p>
<ul>
<li>CPU 运行机制</li>
<li>Intel CPU 历史发展史<ul>
<li>分段</li>
<li>保护模式</li>
</ul>
</li>
<li>特权级<ul>
<li>系统调用</li>
<li>中断</li>
</ul>
</li>
<li>分段内存的优缺点</li>
<li>内存分页</li>
<li>总结</li>
</ul>
<hr>
<h2 id="CPU-运行机制"><a href="#CPU-运行机制" class="headerlink" title="CPU 运行机制"></a>CPU 运行机制</h2><ul>
<li>CPU 是如何工作的呢？它是根据一条条的机器指令来执行的，而<code>机器指令 = 操作码 + 操作数</code>，操作数主要有三类：寄存器地址、内存地址或立即数（即常量）。</li>
<li>我们所熟悉的<strong>程序就是一堆指令和数据的集合</strong>，当打开程序时，装载器把程序中的指令和数据加载到内存中，然后由 CPU 进行取指执行指令。</li>
<li>在内存中是以字节为基本单位来读写数据的，我们可以<strong>把内存看作是一个个的小格子（一般我们称其为内存单元），而每个小格子是一个字节</strong>，那么对于 B8 0123H 这条指令来说，它在内存中占三字节，如下，CPU 该怎么找到这些格子呢，我们需要给这些格子编号，这些编号也就是我们说的<strong>内存地址</strong>，根据内存地址就是可以定位指令所在位置，从而取出里面的数据</li>
</ul>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640.jpeg" alt="640" style="zoom: 67%;">

<p>CPU 执行指令主要分为以下几个步骤：</p>
<ol>
<li><strong>取指令</strong>，CPU 怎么知道要去取哪条指令呢，它里面有一个 IP 寄存器指向了对应要取的指令的内存地址， 然后这个内存地址会通过<strong>地址总线</strong>找到对应的格子，我们把这个过程称为<strong>寻址</strong>，不难发现<strong>寻址能力决定于地址总线的位宽</strong>，假设地址总线位数为 20 位，那么内存的可寻址空间为 2^20 *  1Byte = 1M，将格子（内存单元）里面的数据（指令）取出来后，再通过<strong>数据总线</strong>发往 CPU 中的指令缓存区（指令寄存器），那么一次能传多少数据呢，<strong>取决于数据总线的位宽</strong>，如果数据总线为 16 位，那么一次可以传 16 bit 也就是两个字节。</li>
<li><strong>译码</strong>：指令缓冲区中的指令经过译码以确定该进行什么操作</li>
<li><strong>执行</strong>：译码后会由控制单元向运算器发送控制指令进行操作（比如执行加减乘除等），执行是由运算器操纵数据也就是操作数进行计算，而操作数保存在存储单元（即片内的缓存和寄存器组）中，由于操作数有可能是内存地址，所以执行中可能需要到内存中获取数据（这个过程称为<strong>访存</strong>），执行后的结果保存在寄存器或<strong>写回</strong>内存中</li>
</ol>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/../../秋招/零碎知识点/如何理解“进入内核态”.assets/640-16557030935303.jpeg" alt="图片" style="zoom:67%;">

<p>​    以指令 mov ax, 0123H 为例，它表示将数据 0123H 存到寄存器 AX 中，在此例中 AX 为 16 位寄存器，一次可以操作 16 位也就是 2 Byte 的数据，所以我们将其称为 16 位 CPU，<strong>CPU 是多少位取决于它一次执行指令的数据带宽，而数据带宽又取决于通用寄存器的位宽。</strong></p>
<ol start="4">
<li>   <strong>更新 IP</strong>：执行完一条指令后,更新 IP 中的值，将其指向下一条指令的起始地址，然后重复步骤 1由以上总结可知<strong>寻址能力与寄存器位数有关。</strong></li>
</ol>
<hr>
<p>接下来我们以执行四条指令为例再来仔细看下 CPU 是如何执行指令的，动图如下：</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/../../秋招/零碎知识点/如何理解“进入内核态”.assets/640-16557032084796.gif" alt="图片" style="zoom:50%;">

<p>看到上面这个动图，可能会发现两个问题</p>
<ol>
<li><strong>前文说指令地址是根据 IP 来获取的吗，但上图显示指令地址却是由「CS 左移四位 + IP」计算而来的，与我们所阐述的指令保存在 IP 寄存器中似乎有些出入，这是怎么回事呢？</strong></li>
<li><strong>动图显示的地址是真实物理地址，这样进程之间可以互相访问/改写对方的物理地址，显然是不安全的，那如何才能做到安全访问或者说进程间内存的隔离呢</strong></li>
</ol>
<p>接着往下看。。。</p>
<h2 id="Intel-CPU-历史发展史"><a href="#Intel-CPU-历史发展史" class="headerlink" title="Intel CPU 历史发展史"></a>Intel CPU 历史发展史</h2><p>1971 年世界上第一块 4 位 CPU-4004 微处理器横空出世，1974 年 Intel 研发成功了 8 位 CPU-8080，这两款 CPU <strong>都是使用的绝对物理地址来寻址的</strong>，指令地址只存在于 IP 寄存器中（即只使用 IP 寄存器即可确定内存地址）。由于是使用绝对物理地址寻址，也就意味着进程之间的内存数据可能会互相覆盖，很不安全，所以<strong>这两者只支持单进程</strong></p>
<h3 id="分段"><a href="#分段" class="headerlink" title="分段"></a>分段</h3><p>1978 年英特尔又研究成功了第一款 16  位 CPU - 8086，这款 CPU 可以说是 x86 系列的鼻祖了，设计了 16 位的寄存器和 20 位的地址总线，所以内存地址可以达到 2^20 Byte 即 1M，极大地扩展了地址空间，但是问题来了，由于寄存器只有 16 位，那么 16 位的 IP 寄存器如何能寻址 20 位的地址呢，首先 Intel 工程师设计了一种<strong>分段的方法</strong>：1M 内存可以分为 16 个大小为 <strong>64 K 的段</strong>，那么内存地址就可以由「段的起始地址（也叫<strong>段基址</strong>） + <strong>段内偏移</strong>（IP 寄存器中的值）」组成，对于进程说只需要关心 4 个段 ，<code>代码段</code>，<code>数据段</code>，<code>堆栈段</code>，<code>附加段</code>，这几个段的段基址分别保存在 CS，DS，SS，ES 这四个寄存器中</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-16557034726389.jpeg" alt="图片" style="zoom:50%;">

<p>这四个寄存器也是 16 位，那怎么访问 20 位的内存地址呢，实现也很简单，将每个寄存器的值左移四位，然后再加上段内偏移即为寻址地址，CPU 都是取代码段 中的指令来执行的，我们以代码段内的寻址为例来计算内存地址，指令的地址 = CS &lt;&lt; 4 + IP ，这种方式做到了 20 位的寻址，只要改变 CS，IP 的值，即可实现在 0 到最大地址 0xFFFFF 全部 20 位地址的寻址</p>
<p>举个例子：假设 CS 存的数据为 0x2000,IP 为 0x0003,那么对应的指令地址为</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570353052112.jpeg" alt="图片" style="zoom:50%;">

<p>图示为真实的物理地址计算方式，从中可知， CS 其实保存的是真实物理地址的高 16 位。</p>
<p><strong>分段的初衷是为了解决寻址问题</strong>，但本质上段寄存器中保存的还是真实物理地址的段基础，且可以随意指定，所以它也无法支持多进程，因为这意味着进程可以随意修改 CS：IP 将其指向任意地址，很可能会覆盖正在运行的其他进程的内存，造成灾难性后果。</p>
<p>我们把这种使用真实物理地址且未加任何限制的寻址方式称为<strong>实模式</strong>（real mode，即实际地址模式）</p>
<hr>
<h3 id="保护模式"><a href="#保护模式" class="headerlink" title="保护模式"></a>保护模式</h3><p>实模式上的物理地址由段寄存器中的段基址:IP 计算而来，而段基址可由用户随意指定，显然非常不安全，于是 Intel 在之后推出了 80286 中启用了<strong>保护模式</strong>，这个保护是怎么做的呢</p>
<p>首先<strong>段寄存器保存的不再是段基址了，而是段选择子（Selector）</strong>，其结构如下</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570363170415.jpeg" alt="图片" style="zoom:50%;">

<p>其中第 3 到 15 位保存的是描述符索引，此索引会根据 TI 的值是 0 还是  1 来选择是到 GDT（全局描述符表，一般也称为段表）还是 LDT 来找段描述符，段描述符保存的是段基址和段长度，找到段基址后再加上保存在 IP 寄存器中的段偏移量即为物理地址，段描述符的长度统一为 8 个字节，而 GDT/LDT 表的基地址保存在 gdtr/ldtr 寄存器中，以 GDT （此时 TI 值为 0）为例来看看此时 CPU 是如何寻址的</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/../../秋招/零碎知识点/如何理解“进入内核态”.assets/640-165570369371318.jpeg" alt="图片" style="zoom: 50%;">

<p>可以看到程序中的地址是由段选择子：段内偏移量组成的，也叫<strong>逻辑地址</strong>，在只有分段内存管理的情况下它也被称为<strong>虚拟内存</strong></p>
<p>GDT 及段描述符的分配都是由操作系统管理的，进程也无法更新 CS 等寄存器中值，这样就避免了直接操作其他进程以及自身的物理地址，达到了保护内存的效果，从而为多进程运行提供了可能，我们把这种寻址方式称为<strong>保护模式</strong></p>
<p>那么保护模式是如何实现的呢，细心的你可能发现了上图中在段选择子和段描述符中里出现了 <strong>RPL</strong> 和 <strong>DPL</strong> 这两个新名词，这两个表示啥意思呢？这就涉及到一个概念：<strong>特权级</strong></p>
<h2 id="特权级"><a href="#特权级" class="headerlink" title="特权级"></a>特权级</h2><p>我们知道 CPU 是根据机器指令来执行的，但这些指令有些是非常危险的，比如<strong>清内存</strong>，<strong>置时钟</strong>，<strong>分配系统资源</strong>等，这些指令显然不能让普通的进程随意执行，应该始终控制在操作系统中执行，所以要把操作系统和普通的用户进程区分开来。</p>
<p>我们把一个进程的虚拟地址划分为两个空间，<strong>用户空间</strong>和<strong>内核空间</strong>，用户空间即普通进程所处空间，内核空间即操作系统所处空间。</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570798535721.jpeg" alt="图片" style="zoom:50%;">

<p>当 CPU 运行于用户空间（执行用户空间的指令）时，它处于<strong>用户态</strong>，只能执行普通的 CPU 指令 ，当 CPU 运行于内核空间（执行内核空间的指令）时，它处于<strong>内核态</strong>，可以执行清内存，置时钟，读写文件等特权指令，那怎么区分 CPU 是在用户态还是内核态呢，<strong>CPU 定义了四个特权等级</strong>，如下，从 0 到 3，特权等级依次递减，当特权级为 0 时，CPU 处于内核态，可以执行任何指令，当特权级为 3 时，CPU 处于用户态，<strong>在 Linux 中只用了 Ring  0，Ring 3 两个特权等级</strong>。</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570812564924.jpeg" alt="图片" style="zoom:67%;">

<p>那么问题来了，怎么知道 CPU 处于哪一个特权等级呢，还记得上文中我们提到的段选择子吗？</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570814139727.jpeg" alt="图片" style="zoom:50%;">

<p>其中的 <strong>RPL 表示请求特权（(Requested privilege level)）</strong>我们把当前保存于 CS 段寄存器的段选择子中的 RPL 称为 <strong>CPL（current priviledge level），即当前特权等级</strong>，可以看到 RPL 有两位，刚好对应着 0,1,2,3 四个特权级，而上文提到的 <strong>DPL 表示段描述符中的特权等级（Descriptor privilege level）</strong>知道了这两个概念也就知道保护模式的实现原理了，CPU 会在两个关键点上对内存进行保护</p>
<ol>
<li>目标段选择子被加载时</li>
<li>当通过线性地址（在只有段式内存情况下，线性地址为物理地址）访问一个内存页时。由此可见，保护也反映在内存地址转换的过程之中，既包括分段又包括分页（后文分提到分页）</li>
</ol>
<p><strong>CPU 是怎么保护内存的呢，它会对 CPL，RPL，DPL 进行如下检查</strong></p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570827870530.png" alt="图片" style="zoom: 67%;">

<p>只有 <strong>CPL &lt;= DPL 且 RPL &lt;= DPL</strong> 时，才会加载目标代码段执行，否则会报<strong>一般保护异常 （General-protection exception）</strong></p>
<p>那么特权等级（也就是 CPL）是怎么变化的呢，我们之前说了 CPU 运行于用户空间时，处于用户态，特权等级为 3，运行于内核空间时，处于内核态，特权等级为 0，所以也可以换个问法 CPU 是如何从用户空间切换到内核空间或者从内核空间切换到用户空间的，这就涉及到一个概念：<strong>系统调用</strong>。</p>
<h3 id="系统调用"><a href="#系统调用" class="headerlink" title="系统调用"></a>系统调用</h3><p>我们知道用户进程虽然不能执行特权指令，但有时候也需要执行一些读写文件，发送网络包等操作，而这些操作又只能让操作系统来执行，那该怎么办呢，可以让<strong>操作系统提供接口，让用户进程来调用</strong>即可，我们把这种方式叫做<strong>系统调用</strong>，系统调用可以直接由应用程序调用，或者通过调用一些公用函数库或 shell（这些函数库或 shell 都封装了系统调用接口）等也可以达到间接调用系统调用的目的。通过系统调用，应用程序实现了<strong>陷入（trap）内核态</strong>的目的，这样就从用户态切换到了内核态中，如下</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570843564333.jpeg" alt="图片" style="zoom: 67%;">

<p>那么系统调用又是怎么实现的呢，主要是靠<strong>中断</strong>实现的，接下来我们就来了解一下什么是中断</p>
<h3 id="中断"><a href="#中断" class="headerlink" title="中断"></a>中断</h3><p>陷入内核态的系统调用主要是通过一种 <strong>trap gate</strong>（陷阱门）来实现的，它其实是<strong>软件中断</strong>的一种，由 CPU 主动触发给自己一个中断向量号，然后 CPU 根据此中断向量号就可以去中断向量表找到对应的<strong>门描述符</strong>，门描述符与 GDT 中的段描述符相似，也是 8 个字节，门描述符中包含段选择子，段内偏移，DPL 等字段 ，然后再根据段选择子去 GDT（或者 LDT，下图以 GDT 为例） 中查找对应的段描述符，再找到段基地址，然后根据中断描述符表的段内偏移即可找到中断处理例程的入口点,整个中断处理流程如下</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570856127136.jpeg" alt="图片" style="zoom: 67%;">

<p>当然了，不是随便发一个中断向量都能被执行，只有满足一定条件的中断才允许被普通的应用程序调用，从发出软件中断再到执行中断对应的代码段会做如下的检查</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570864262139.jpeg" alt="图片" style="zoom:50%;">

<p>一般应用程序发出软件中断对应的向量号是大家熟悉的 int 0x80（int 代表 interrupt），它的门描述符中的 DPL 为 3,所以能被所有的用户程序调用，而它对应的目标代码段描述符中的 DPL 为 0，所以当通过中断门检查后（即 CPL &lt;= 门描述符中的 DPL 成立），CPU 就会将 CS 寄存器中的 RPL（3） 替换为目标代码段描述符的 DPL（0），替换后的 CPL 也就变成了 0，通过这种方式完成了从用户态到内核态的替换，当中断代码执行后执行 iret 指令又会切换回用户态</p>
<p><strong>另外当执行中断程序时，还需要首先把当前用户进程中对应的堆栈，返回地址等信息保存，以便切回到用户态时能恢复现场</strong></p>
<p>可以看到 int 80h 这种软件中断的执行又是检查特权级，又是从用户态切换到内核态，又是保存寄存器的值，可谓是非常的耗时，光看一下以下图示就知道像 int 0x80 这样的软件中断开销是有多大了</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165570876179242.png" alt="图片" style="zoom:67%;">

<p>所以后来又开发出了 <strong>SYSENTER<code>/</code>SYSCALL</strong> 这样快速系统调用的指令，它们取消了权限检查，也不需要在中断描述表（Interrupt Descriptor Table、IDT）中查找系统调用对应的执行过程，也不需要保存堆栈和返回地址等信息，而是直接进入<em>CPL 0</em>，并将新值加载到与代码和堆栈有关的寄存器当中（cs，eip，ss 和 esp），所以极大地提升了性能。</p>
<hr>
<h2 id="分段内存的优缺点"><a href="#分段内存的优缺点" class="headerlink" title="分段内存的优缺点"></a>分段内存的优缺点</h2><p>使用了保护模式后，程序员就可以在代码中使用了<strong>段选择子：段偏移量</strong>的方式来寻址，这不仅让多进程运行成为了可能，而且也解放了程序员的生产力，<strong>我们完全可以认为程序拥有所有的内存空间（虚拟空间），因为段选择子是由操作系统分配的，只要操作系统保证不同进程的段的虚拟空间映射到不同的物理空间上，不要重叠即可，也就是说虽然各个程序的虚拟空间是一样的，但由于它们映射的物理地址是不同且不重叠的，所以是能正常工作的，但是为了方便映射，一般要求在物理空间中分配的段是连续的（这样只要维护映射关系的起始地址和对应的空间大小即可）</strong></p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165573477906445.jpeg" alt="图片" style="zoom: 67%;">

<p>但段式内存管理缺点也很明显：内存碎片可能很大，举个例子</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165573479129248.jpeg" alt="图片" style="zoom:67%;">

<p>如上图示，连续加载了三个程序到内存中，如果把 Chrome 关闭了，此时内存中有两段 128 M的空闲内存，但如果此时要加载一个 192 M 的程序 X 却有心无力了 ，因为段式内存需要划分出一块<strong>连续的</strong>内存空间，此时你可以选择把占 256 M 的 Python 程序先 swap 到磁盘中，然后紧跟着 512 M 内存的后面划分出 256 M 内存，再给 Python 程序 swap 到这块物理内存中，这样就腾出了连续的 256 M 内存，从而可以加载程序 X 了，但这种频繁地将几十上百兆内存与硬盘进行 swap 显然会对性能造成严重的影响，毕竟谁都知道内存和硬盘的读写速度可是一个天上一个地上，如果一定要交换，能否每次 swap 得能少一点，比如只有几 K，这样就能满足我们的需求，<strong>分页内存管理</strong>就诞生了</p>
<h2 id="内存分页"><a href="#内存分页" class="headerlink" title="内存分页"></a>内存分页</h2><p>1985 年 intel 推出了 32 位处理器 80386，也是首款支持分页内存的 CPU</p>
<p>和分段这样连续分配一整段的空间给程序相比，<strong>分页是把整个物理空间切成一段段固定尺寸的大小</strong>，当然为了映射，<strong>虚拟地址也需要切成一段段固定尺寸的大小</strong>，这种固定尺寸的大小我们一般称其为页，<strong>在 Linux 中一般每页的大小为 4KB，这样虚拟地址和物理地址就通过页来映射起来了</strong></p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165573494365051.jpeg" alt="图片" style="zoom: 67%;">

<p>当然了<strong>这种映射关系是需要一个映射表来记录的，这样才能把虚拟地址映射到物理内存中，给定一个虚拟地址，它最终肯定在某个物理页内，所以虚拟地址一般由「页号+页内偏移」组成，而映射表项需要包含物理内存的页号，这样只要将页号对应起来，再加上页内偏移，即可获取最终的物理内存。</strong></p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165573511291954.jpeg" alt="图片" style="zoom:67%;">

<p>于是问题来了，映射表（也称页表）该怎么设计呢,我们以 32 位虚拟地址位置来看看，假设页大小为 4K（2^12），那么至少需要 2^20 也就是 100 多万个页表项才能完全覆盖所有的虚拟地址，假设每一个页表项 4 个字节，那就意味着为一个进程的虚拟地址就需要准备 2^20 * 4 B = 4 M 的页表大小，如果有 100 个进程，就意味着光是页表就要占用 400M 的空间了，这显然是非常巨大的开销，那该怎么解决这个页表空间占用巨大的问题呢？</p>
<p>我们注意到现在的做法是一次性为进程分配了占用其所有虚拟空间的页表项，但实际上一个进程根本用不到这么巨大的虚拟空间，所以这种分配方式无疑导致很多分配的页表项白白浪费了，那该怎么办，答案是<strong>分级管理，等真正需要分配物理空间的时候再分配</strong>，其实大家可以想想我们熟悉的 windows 是怎么分配的，是不是一开始只分配了 C 盘，D盘，E盘，等要存储的时候，先确定是哪个盘，再在这个盘下分配目录，然后再把文件存到这个目录下，并不会一开始就把所有盘的空间给分配完的。</p>
<p>同样的道理，以 32 位虚拟地址为例，我们也可以对页表进行分级管理, 页表项 2^20 = 2^10 * 2^10 = 1024 * 1024，我们把一个页表分成两级页表，第一级页表 1024 项，每一项都指向一个包含有 1024 个页表项的二级页表</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165573551550457.jpeg" alt="图片" style="zoom:50%;">

<p>这样<strong>只有在一级页表中的页表项被分配的时候才会分配二级页表，极大的节省了空间</strong>，我们简单算下，假设 4G 的虚拟空间进程只用了 20%（已经很大了，大部分用不到这么多），那么由于一级页表空间为 1024 *4 = 4K，总的页表空间为 4K+ 0.2 * 4M = 0.804M，相比于原来的 4M 是个巨大的提升！</p>
<p>那么<strong>对于分页保护模式又是如何起作用的呢</strong>，同样以 32 位为例，它的二级页表项（也称 page table entry）其实是以下结构</p>
<img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165573556006364.jpeg" alt="图片" style="zoom: 67%;">

<p>注意第三位（也就是 2 对应的位置）有个 <strong>U/S，它其实就是代表特权级</strong>，表示的是用户/超级用户标志。为 <strong>1 时，允许所有特权级别的程序访问；为 0 时，仅允许特权级为0、1、2（Linux 中没有 1，2）的程序（也就是内核）访问。</strong>页目录中的这个位对其所映射的所有页面起作用</p>
<p>既然分页这么好，那么分段是不是可以去掉了呢，理论上确实可以，但 Intel 的 CPU 严格执行了 backward compatibility（回溯兼容），也就是说最新的 CPU 永远可以运行针对早期 CPU 开发的程序，否则早期的程序就得针对新 CPU 架构重新开发了（早期程序针对的是 CPU 的段式管理进行开发），这无论对用户还是开发者都是不能接受的（别忘了安腾死亡的一大原因就是由于不兼容之前版本的指令），兼容性虽然意味着每款新的 CPU 都得兼容老的指令，所背的历史包袱越来越重，但对程序来说能运行肯定比重新开发好，所以既然早期的 CPU 支持段，那么自从 80386 开始的所有 CPU 也都得支持段，而分页反而是可选的，也就意味着这些 <strong>CPU 的内存管理都是段页式管理，逻辑地址要先经过段式管理单元转成线性地址（也称虚拟地址），然后再经过页式管理单元转成物理内存</strong>，如下</p>
<p><img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165573569185867-165573569467269.jpeg" alt="640-165573569185867-165573569467269"></p>
<p>在 Linux 中，虽然也是段页式内存管理，但它统一把 CS，DS，SS，ES 的段基址设置为了 0，段界限也设置为了整个虚拟内存的长度，所有段都分布在同一个地址空间，这种内存模式也叫<strong>平坦内存模型（flat memory model）</strong></p>
<p><img src="/2022/06/20/%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E2%80%9C%E8%BF%9B%E5%85%A5%E5%86%85%E6%A0%B8%E6%80%81%E2%80%9D/640-165573570355770-165573570501972.png" alt="640-165573570355770-165573570501972"></p>
<p>我们知道逻辑地址由段选择子：段内偏移地址组成，既然段选择子指向的段基地址为 0，那也就意味着段内偏移地址即为即为线性地址（也就是虚拟地址），由此可知 <strong>Linux 中所有程序的代码都使用了虚拟地址，通过这种方式巧妙地绕开了分段管理，分段只起到了访问控制和权限的作用（别忘了各种权限检查依赖 DPL，RPL 等特权字段，特权极转移也依赖于段选择子中的 DPL 来切换的）</strong></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>简单总结一下，CPU 诞生之后，使用的绝对物理内存来寻址（也就是实模式），随后随着 8086 的诞生，由于工艺的原因，虽然地址总线是 20 位，但寄存器却只有 16 位，一个难题出现了，16 位的寄存器该怎么寻址 20 位的内存地址呢，于是段的概念被提出了，段的出现虽然解决了寻址问题，但本质上 CS &lt;&lt; 4 + IP 的寻址方式依然还是绝对物理地址，这样的话由于地址会互相覆盖，显然无法做到多进程运行，于是<strong>保护模式</strong>被提出了，保护就是为了物理内存免受非法访问，于是用户空间，内核空间，特权级也被提出来了，段寄存器里保存的不再是段基址，而是<strong>段选择子</strong>，由操作系统分配，用户也无法随意修改段选择子，必须通过中断的形式才能从用户态陷入内核态，中断执行的过程也需要经历特权级的检查，检查通过之后特权级从 3 切换到了 0，于是就可以放心合法的执行特权指令了。可以看到，通过操作系统分配段选择子+中断的方式内存得到了有效保护，但是分段可能造成内存碎片过大以致频繁 swap 会影响性能的问题，于是分页出现了，保护模式+分页终于可以让多进程，高效调度成为了可能。</p>
<blockquote>
<p> 注：参考自程序员cxuan的文章《非常硬核的解释！》</p>
</blockquote>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/">C++中的内存泄漏</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2022-05-13</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</a><i class="fa fa-angle-right" aria-hidden="true"></i><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/C/">C++</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/C/">C++</a></span><div class="content"><h1 id="C-内存泄漏原因、避免及定位"><a href="#C-内存泄漏原因、避免及定位" class="headerlink" title="C++内存泄漏原因、避免及定位"></a>C++内存泄漏原因、避免及定位</h1><p>对于C/C++开发人员来说，内存泄漏是最容易遇到的问题之一，众所周知，这是由C/C++语言的特性引起的。</p>
<p>C/C++语言与其他语言不同，需要开发者去申请和释放内存，即需要开发者去管理内存，如果内存使用不当，就容易造成<strong>段错误（segment fault）</strong>或者<strong>内存泄漏（memory leak）</strong>。</p>
<p>本文将针对C++项目中经常遇到的导致内存泄漏的原因，如何避免和定位内存泄漏进行分析。</p>
<p>主要内容如下：</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/C++内存泄漏.png" alt="C++内存泄漏" style="zoom:50%;">

<hr>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>C/C++语言中，内存的分配与回收都是由开发人员在编写代码时主动完成的，这样带来的<strong>好处</strong>是内存管理的开销较小，程序拥有更高的执行效率；<strong>弊端</strong>是依赖于开发者的水平，随着代码规模的扩大，极容易遗漏释放内存的步骤，或者一些不规范的编程可能会使程序具有安全隐患。</p>
<p>如果对内存管理不当，可能导致程序中存在内存缺陷，甚至会在运行时产生内存故障错误。内存泄漏是各类缺陷中十分棘手的一种，对系统的稳定运行威胁较大。</p>
<blockquote>
<p><strong>定义：当动态分配的内存在程序结束之前没有被回收时，则发生了内存泄漏。</strong></p>
</blockquote>
<p>由于系统软件，如操作系统、编译器、开发环境等都是由C/C++语言实现的，不可避免地存在内存泄漏缺陷，特别是一些在服务器上长期运行的软件，若存在内存泄漏则会造成严重后果，例如<strong>性能下降、程序终止、系统崩溃、无法提供服务</strong>。</p>
<hr>
<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><ul>
<li><strong>内存泄漏（Memory Leak）是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放，造成系统内存的浪费，导致程序运行速度减慢甚至系统崩溃等严重后果。</strong></li>
<li>当我们在程序中对原生指针(raw pointer)使用<code>new</code>操作符或者<code>free</code>函数的时候，实际上是在堆上为其分配内存，这个内存指的是RAM，而不是硬盘等永久存储。持续申请而不释放(或者少量释放)内存的应用程序，最终因内存耗尽导致<strong>内存溢出OOM（Out Of Memory）</strong>。</li>
</ul>
<blockquote>
<p><strong>定义：内存溢出：(Out Of Memory，简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多，最终使得程序运行要用到的内存大于能提供的最大内存。此时程序就运行不了，系统会提示内存溢出，有时候会自动关闭软件，重启电脑或者软件后释放掉一部分内存又可以正常运行该软件，而由系统配置、数据流、用户代码等原因而导致的内存溢出错误，即使用户重新执行任务依然无法避免。</strong></p>
</blockquote>
<h2 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h2><p>内存泄漏分为以下两类：</p>
<ul>
<li><strong>堆内存泄漏</strong>：我们经常说的内存泄漏就是堆内存泄漏，在堆上申请了资源，在结束使用的时候，没有释放归还给OS，从而导致该块内存永远不会被再次使用。</li>
<li><strong>资源泄漏</strong>：通常指的是系统资源，比如socket，文件描述符等，因为这些在系统中都是有限制的，如果创建了而不归还，久而久之，就会耗尽资源，导致其他程序不可用。</li>
</ul>
<p>本文主要分析堆内存泄漏，所以后面的内存泄漏均指的是<strong>堆内存泄漏</strong>。</p>
<hr>
<h2 id="根源"><a href="#根源" class="headerlink" title="根源"></a>根源</h2><h3 id="C-11引入智能指针来避免内存泄漏"><a href="#C-11引入智能指针来避免内存泄漏" class="headerlink" title="C++11引入智能指针来避免内存泄漏"></a>C++11引入智能指针来避免内存泄漏</h3><ul>
<li>内存泄漏，主要指的是在堆(heap)上申请的动态内存泄漏，或者说是指针指向的内存块忘了被释放，导致该块内存不能再被申请重新使用。</li>
<li>C++11引入了智能指针（unique_ptr、shared_ptr、weak_ptr）来对原生指针进行了一次封装，采用引用计数的方式来自动对不再使用的内存进行释放，从而使得程序员不用再显式调用delete释放内存了。但是内存泄漏的问题还是不能得到完全避免，最重要的一个原因是你不能保证组内其他人不使用原生指针，更不能保证合作部门不使用指针。</li>
<li>那么为什么C/C++中会存在指针呢？这就得从进程的内存布局说起。</li>
</ul>
<hr>
<h3 id="进程的内存布局"><a href="#进程的内存布局" class="headerlink" title="进程的内存布局"></a>进程的内存布局</h3><p>我们来看一下进程虚拟内存空间的布局：</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/640.png" alt="640" style="zoom:50%;">

<p>上图为32位进程的内存布局，从上图中主要包含以下几个块：</p>
<ul>
<li><p><strong>内核空间</strong>：供内核使用，存放的是内核代码和数据，负责进程的内存管理、进程管理、设备驱动管理和文件管理。</p>
</li>
<li><p><strong>stack</strong>：这就是我们经常所说的栈，用来存储自动变量(automatic variable)。</p>
</li>
<li><p><strong>mmap</strong>: 也成为内存映射，用来在进程虚拟内存地址空间中分配地址空间，创建和物理内存的映射关系。</p>
</li>
<li><p><strong>heap</strong>:就是我们常说的堆，动态内存的分配都是在堆上。</p>
</li>
<li><p><strong>bss</strong>:包含所有未初始化的全局和静态变量，此段中的所有变量都由0或者空指针初始化，程序加载器在加载程序时为BSS段分配内存。</p>
</li>
<li><p><strong>data</strong>:初始化的数据块：</p>
</li>
<li><ul>
<li>包含显式初始化的全局变量和静态变量</li>
<li>此段的大小由程序源代码中值的大小决定，在运行时不会更改</li>
<li>它具有读写权限，因此可以在运行时更改此段的变量值</li>
<li>该段可进一步分为初始化只读区和初始化读写区</li>
</ul>
</li>
<li><p><strong>text</strong>：也称为文本段</p>
</li>
<li><ul>
<li>该段包含已编译程序的二进制文件。</li>
<li>该段是一个只读段，用于防止程序被意外修改</li>
<li>该段是可共享的，因此对于文本编辑器等频繁执行的程序，内存中只需要一个副本</li>
</ul>
</li>
</ul>
<p>由于本文主要讲内存分配相关，所以下面的内容仅涉及到**栈(stack)<strong>和</strong>堆(heap)**。</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/2.png" alt="2" style="zoom:50%;">

<hr>
<h3 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h3><p>栈一块<strong>连续</strong>的内存块，栈上的内存分配就是在这一块连续内存块上进行操作的。<strong>编译器在编译的时候，就已经知道要分配的内存大小</strong>，当调用函数时候，其内部的遍历都会在栈上分配内存；当结束函数调用时候，内部变量就会被释放，进而将内存归还给栈。</p>
<p>比如下面的程序：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Object</span> &#123;</span></span><br><span class="line">  <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Object</span>() = <span class="keyword">default</span>;</span><br><span class="line">    <span class="comment">// ....</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Object obj;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述代码中，obj就是在栈上进行分配，当出了fun作用域的时候，会自动调用Object的析构函数对其进行释放。</p>
<ul>
<li>前面有提到，局部变量会在作用域（如函数作用域、块作用域等）结束后析构、释放内存。因为分配和释放的次序是刚好完全相反的，所以可用到堆栈<strong>先进后出（first-in-last-out, FILO）</strong>的特性，而 C++ 语言的实现一般也会使用到<strong>调用堆栈（call stack）</strong>来分配局部变量（但非标准的要求）。</li>
<li>因为栈上内存分配和释放，是一个进栈和出栈的过程(对于编译器只是一个指令)，所以<strong>相比于堆上的内存分配，栈要快的多。</strong></li>
<li>虽然栈的访问速度要快于堆，<strong>每个线程都有一个自己的栈，栈上的对象是不能跨线程访问的</strong>，这就决定了栈空间大小是有限制的，如果栈空间过大，那么在大型程序中几十乃至上百个线程，光栈空间就消耗了RAM，这就导致heap的可用空间变小，影响程序正常运行。</li>
</ul>
<hr>
<h4 id="栈的设置"><a href="#栈的设置" class="headerlink" title="栈的设置"></a>栈的设置</h4><p>在Linux系统上，可用通过如下命令来查看栈大小（单位为KB）：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ulimit -s</span><br><span class="line">10240</span><br></pre></td></tr></table></figure>

<p>可以通过shell命令修改栈大小（临时修改）：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ulimit -s 102400</span><br></pre></td></tr></table></figure>

<p>也可以修改<code>/etc/security/limits.conf</code>的内容，来永久修改。</p>
<hr>
<h4 id="栈的分配方式"><a href="#栈的分配方式" class="headerlink" title="栈的分配方式"></a>栈的分配方式</h4><p><strong>静态分配</strong></p>
<p>静态分配由编译器完成，例如局部变量以及函数参数等，都在编译期就分配好了。如下：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">int</span> a[<span class="number">10</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码中，a占<code>10 * sizeof(int)</code>个字节，在<strong>编译的时候直接计算好</strong>了，运行的时候，直接进栈出栈。</p>
<p><strong>动态分配</strong></p>
<p>可能很多人认为只有堆上才会存在动态分配，在栈上只可能是静态分配。其实，这个观点是错的，<strong>栈上也支持动态分配</strong>（敲黑板，划重点！！），该<strong>动态分配由alloca()函数进行分配</strong>。栈的动态分配和堆是不同的，<strong>通过alloca()函数分配的内存由编译器进行释放，无需手动操作。</strong></p>
<hr>
<h4 id="栈内存的特点"><a href="#栈内存的特点" class="headerlink" title="栈内存的特点"></a>栈内存的特点</h4><ul>
<li><strong>分配速度快</strong>：分配大小由编译器在编译期完成</li>
<li><strong>不会产生内存碎片</strong>：栈内存分配是连续的，以FILO的方式进栈和出栈</li>
<li><strong>大小受限</strong>：栈的大小依赖于操作系统</li>
<li><strong>访问受限</strong>：只能在当前函数或者作用域内进行访问</li>
</ul>
<hr>
<h3 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h3><p>堆（heap）是一种内存管理方式。内存管理对操作系统来说是一件非常复杂的事情，因为首先内存容量很大，其次就是内存需求在时间和大小块上没有规律（操作系统上运行着几十甚至几百个进程，这些进程可能随时都会申请或者是释放内存，并且申请和释放的内存块大小是随意的）。</p>
<p>堆这种内存管理方式的特点就是<strong>自由（随时申请、随时释放、大小块随意）</strong>。堆内存是操作系统划归给<strong>堆管理器</strong>（操作系统中的一段代码，属于操作系统的内存管理单元）来管理的，<strong>堆管理器提供了对应的接口_sbrk、_mmap等</strong>，只是该接口往往由<strong>运行时库(Linux为glibc)<strong>进行调用，即也可以说由运行时库进行堆内存管理，</strong>运行时库提供了malloc/free函数由开发人员调用</strong>，进而使用堆内存。</p>
<hr>
<h4 id="堆的分配方式"><a href="#堆的分配方式" class="headerlink" title="堆的分配方式"></a>堆的分配方式</h4><p>正如我们所理解的那样，由于是在运行期进行内存分配，<strong>分配的大小也在运行期才会知道</strong>，所以<strong>堆只支持动态分配</strong>，内存申请和释放的行为由开发者自行操作，这就<strong>很容易造成我们说的内存泄漏</strong>。</p>
<hr>
<h4 id="堆内存的特点"><a href="#堆内存的特点" class="headerlink" title="堆内存的特点"></a>堆内存的特点</h4><ul>
<li>变量可以在进程范围内访问，即进程内的所有线程都可以访问该变量</li>
<li>没有内存大小限制，这个其实是相对的，只是相对于栈大小来说没有限制，其实最终还是受限于RAM</li>
<li>相对栈来说访问比较慢</li>
<li>内存碎片</li>
<li>由开发者管理内存，即内存的申请和释放都由开发人员来操作</li>
</ul>
<h3 id="堆和栈的区别"><a href="#堆和栈的区别" class="headerlink" title="堆和栈的区别"></a>堆和栈的区别</h3><p><strong>对于栈来讲，是由编译器自动管理，无需我们手工控制；</strong></p>
<p><strong>对于堆来说，释放工作由程序员控制，容易产生memory leak。</strong></p>
<p>以下是堆和栈的主要区别：</p>
<ul>
<li><p><strong>空间大小不同</strong></p>
<ul>
<li>一般来讲在 32 位系统下，堆内存可以达到3G的空间，从这个角度来看堆内存几乎是没有什么限制的。</li>
<li>对于栈来讲，一般都是有一定的空间大小的，一般依赖于操作系统(也可以人工设置)</li>
</ul>
</li>
<li><p><strong>能否产生碎片不同</strong></p>
<ul>
<li>对于堆来讲，频繁的内存分配和释放势必会造成内存空间的不连续，从而造成大量的碎片，使程序效率降低。</li>
<li>对于栈来讲，内存都是连续的，申请和释放都是指令移动，类似于数据结构中的进栈和出栈</li>
</ul>
</li>
<li><p><strong>增长方向不同</strong></p>
<ul>
<li>对于堆来讲，生长方向是向上的，也就是向着内存地址增加的方向</li>
<li>对于栈来讲，它的生长方向是向下的，是向着内存地址减小的方向增长</li>
</ul>
</li>
<li><p><strong>分配方式不同</strong></p>
<ul>
<li>堆都是动态分配的，比如我们常见的malloc/new；而栈则有静态分配和动态分配两种。</li>
<li>静态分配是编译器完成的，比如局部变量的分配，而栈的动态分配则通过alloca()函数完成</li>
<li>二者动态分配是不同的，栈的动态分配的内存由编译器进行释放，而堆上的动态分配的内存则必须由开发人自行释放</li>
</ul>
</li>
<li><p><strong>分配效率不同</strong></p>
<ul>
<li>栈有操作系统分配专门的寄存器存放栈的地址，压栈出栈都有专门的指令执行，这就决定了栈的效率比较高</li>
<li>堆内存的申请和释放专门有运行时库提供的函数，里面涉及复杂的逻辑，申请和释放效率低于栈</li>
</ul>
</li>
</ul>
<p>讲到这里，栈和堆的基本特性以及各自的优缺点、使用场景已经分析完成，在这里给开发者一个建议，能使用栈的时候，就尽量使用栈，一方面是因为效率高于堆，另一方面内存的申请和释放由编译器完成，这样就避免了很多问题。</p>
<hr>
<p><strong>那为什么会有堆这个概念呢？</strong></p>
<p>既然栈的分配效率高，而且自动释放，但是空间有限，那么将栈空间扩展不就好了吗？</p>
<p>其实不然，<strong>虽然栈效率比较高，且不存在内存泄漏、内存碎片等，但是由于其本身的局限性(不能多线程、大小受限)，所以在很多时候，还是需要在堆上进行内存。</strong></p>
<p>我们来看一段代码：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">int</span> a;</span><br><span class="line">  <span class="keyword">int</span> *p;</span><br><span class="line">  p = (<span class="keyword">int</span> *)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">int</span>));</span><br><span class="line">  <span class="built_in">free</span>(p);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码很简单，有两个变量a和p，类型分别为int和int *，其中，a和p存储在栈上，p的值为在堆上的某块地址(在上述代码中，p的<strong>值</strong>为0x1c66010)，上述代码布局如下图所示：</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/640-16524049915771.png" alt="640-16524049915771" style="zoom:50%;">

<hr>
<h2 id="内存泄漏的产生方式"><a href="#内存泄漏的产生方式" class="headerlink" title="内存泄漏的产生方式"></a>内存泄漏的产生方式</h2><p>以产生的方式来分类，内存泄漏可以分为四类:</p>
<ul>
<li>常发性内存泄漏</li>
<li>偶发性内存泄漏</li>
<li>一次性内存泄漏</li>
<li>隐式内存泄漏</li>
</ul>
<hr>
<h4 id="常发性内存泄漏"><a href="#常发性内存泄漏" class="headerlink" title="常发性内存泄漏"></a><strong>常发性内存泄漏</strong></h4><p>产生内存泄漏的代码或者函数会被多次执行到，在每次执行的时候，都会产生内存泄漏。</p>
<hr>
<h4 id="偶发性内存泄漏"><a href="#偶发性内存泄漏" class="headerlink" title="偶发性内存泄漏"></a><strong>偶发性内存泄漏</strong></h4><p>与常发性内存泄漏不同的是，偶发性内存泄漏函数只在特定的场景下才会被执行。</p>
<p>偶发性内存泄漏常常是当程序运行中满足了某个不常出发的条件，调用了某个函数（或执行了某块代码），而这块代码中，存在内存泄漏的问题而导致的，也就是说偶发性内存泄漏不会每次执行都产生，往往需要测试人员及时发现。</p>
<hr>
<h4 id="一次性内存泄漏"><a href="#一次性内存泄漏" class="headerlink" title="一次性内存泄漏"></a>一次性内存泄漏</h4><p>这种内存泄漏在程序的生命周期内只会泄漏一次，或者说<strong>造成泄漏的代码只会被执行一次。</strong></p>
<p>有的时候，这种可能不算内存泄漏，或者说设计如此。比如如下服务程序：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">auto</span> *service = <span class="keyword">new</span> Service;</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  service-&gt;<span class="built_in">Run</span>();<span class="comment">// 服务启动</span></span><br><span class="line">  service-&gt;<span class="built_in">Loop</span>(); <span class="comment">// 可以理解为一个sleep，目的是使得程序不退出</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这种严格意义上，并不算内存泄漏，因为程序是这么设计的，即使程序异常退出，那么整个服务进程也就退出了，当然，在Loop()后面加个delete更好。</p>
<hr>
<h4 id="隐式内存泄漏"><a href="#隐式内存泄漏" class="headerlink" title="隐式内存泄漏"></a>隐式内存泄漏</h4><p><strong>程序在运行过程中不停的分配内存，但是直到结束的时候才释放内存。</strong>严格的说这里并没有发生内存泄漏，因为最终程序释放了所有申请的内存。但是对于一个服务器程序，需要运行几天，几周甚至几个月，不及时释放内存也可能导致最终耗尽系统的所有内存。所以，我们称这类内存泄漏为隐式内存泄漏。</p>
<p>比较常见的隐式内存泄漏有以下三种：</p>
<ul>
<li><strong>内存碎片</strong>：可以看下一篇文章《深入理解glibc内存管理精髓》，程序跑了几天之后，进程就因为OOM导致了退出，就是因为内存碎片导致剩下的内存不能被重新分配导致</li>
<li><strong>即使我们调用了free/delete，运行时库不一定会将内存归还OS</strong>，具体《深入理解glibc内存管理精髓》</li>
<li>用过STL的知道，<strong>STL</strong>内部有一个<strong>自己的allocator</strong>，我们可以当做一个<strong>memory poll</strong>，当调用vector.clear()时候，内存并不会归还OS，而是放回allocator，其内部根据一定的策略，在特定的时候将内存归还OS，跟glibc原理很像。</li>
</ul>
<hr>
<h2 id="内存泄漏的分类"><a href="#内存泄漏的分类" class="headerlink" title="内存泄漏的分类"></a>内存泄漏的分类</h2><p>内存泄漏产生的原因有很多，明确产生原因是我们解决内存泄漏问题的关键。</p>
<h3 id="未释放"><a href="#未释放" class="headerlink" title="未释放"></a>未释放</h3><p>最常见的内存泄漏就是开辟了堆内存空间，但是忘记释放，比如以下的程序：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">char</span> * pBuffer = <span class="built_in">malloc</span>(<span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">char</span>));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* Do some work */</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面代码是非常常见的内存泄漏场景(也可以使用new来进行分配)，我们申请了一块内存，但是在fun函数结束时候没有调用free函数进行内存释放。</p>
<p>在C++开发中，还有一种内存泄漏，如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Obj</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">   <span class="built_in">Obj</span>(<span class="keyword">int</span> size) &#123;</span><br><span class="line">     buffer_ = <span class="keyword">new</span> <span class="keyword">char</span>;</span><br><span class="line">   &#125;</span><br><span class="line">   ~<span class="built_in">Obj</span>()&#123;&#125;</span><br><span class="line">  <span class="keyword">private</span>:</span><br><span class="line">   <span class="keyword">char</span> *buffer_;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Object obj;</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面这段代码中，析构函数没有释放成员变量buffer_指向的内存，所以<strong>在编写析构函数的时候，一定要仔细分析成员变量有没有申请动态内存，如果有，则需要手动释放，我们重新编写了析构函数</strong>，如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">~<span class="built_in">Object</span>() &#123;</span><br><span class="line">  <span class="keyword">delete</span> buffer_;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>在C/C++中，对于普通函数，如果申请了堆资源，请根据代码的具体场景调用free/delete进行资源释放；对于class，如果申请了堆资源，则需要在对应的析构函数中调用free/delete进行资源释放。</strong></p>
<hr>
<h3 id="未分配"><a href="#未分配" class="headerlink" title="未分配"></a>未分配</h3><p>在C++中，我们经常使用new操作符来进行内存分配，其内部主要做了两件事：</p>
<ol>
<li>通过operator new从堆上申请内存(glibc下，operator new底层调用的是malloc)</li>
<li>调用构造函数(如果操作对象是一个class的话)</li>
</ol>
<p>对应的，使用delete操作符来释放内存，其顺序正好与new相反：</p>
<ol>
<li>调用对象的析构函数(如果操作对象是一个class的话)</li>
<li>通过operator delete释放内存</li>
</ol>
<p>比如以下代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="keyword">operator</span> <span class="title">new</span><span class="params">(std::<span class="keyword">size_t</span> size)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">void</span>* p = <span class="built_in">malloc</span>(size);</span><br><span class="line">    <span class="keyword">if</span> (p == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span>(<span class="string">&quot;new failed to allocate %zu bytes&quot;</span>, size);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span>* <span class="keyword">operator</span> <span class="keyword">new</span>[](std::<span class="keyword">size_t</span> size) &#123;</span><br><span class="line">    <span class="keyword">void</span>* p = <span class="built_in">malloc</span>(size);</span><br><span class="line">    <span class="keyword">if</span> (p == <span class="literal">nullptr</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span>(<span class="string">&quot;new[] failed to allocate %zu bytes&quot;</span>, size);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span>  <span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="keyword">void</span>* ptr)</span> <span class="title">throw</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">free</span>(ptr);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span>  <span class="keyword">operator</span> <span class="keyword">delete</span>[](<span class="keyword">void</span>* ptr) <span class="keyword">throw</span>() &#123;</span><br><span class="line">    <span class="built_in">free</span>(ptr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为了加深对于new和delete的内部逻辑的理解，我们再举一个例子：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Test</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">   <span class="built_in">Test</span>() &#123;</span><br><span class="line">     std::cout &lt;&lt; <span class="string">&quot;in Test&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="comment">// other</span></span><br><span class="line">   ~<span class="built_in">Test</span>() &#123;</span><br><span class="line">     std::cout &lt;&lt; <span class="string">&quot;in ~Test&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Test *t = <span class="keyword">new</span> Test;</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  <span class="keyword">delete</span> t;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述main函数中，我们使用new 操作符创建一个Test类指针</p>
<ol>
<li>通过operator new申请内存(底层malloc实现)</li>
<li>通过placement new在上述申请的内存块上调用构造函数</li>
<li>调用ptr-&gt;~Test()释放Test对象的成员变量</li>
<li>调用operator delete释放内存</li>
</ol>
<p>上述过程，可以理解为如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// new</span></span><br><span class="line"><span class="keyword">void</span> *ptr = <span class="built_in">malloc</span>(<span class="built_in"><span class="keyword">sizeof</span></span>(Test));</span><br><span class="line">t = <span class="built_in"><span class="keyword">new</span></span>(ptr)Test</span><br><span class="line">  </span><br><span class="line"><span class="comment">// delete</span></span><br><span class="line">ptr-&gt;~<span class="built_in">Test</span>();</span><br><span class="line"><span class="built_in">free</span>(ptr);</span><br></pre></td></tr></table></figure>

<hr>
<p>上面我们讲解了C++中new和delete操作符的基本实现和内部逻辑，在清楚了这些后，我们来分析一下产生内存泄漏的几种类型。</p>
<hr>
<h4 id="new-和-free"><a href="#new-和-free" class="headerlink" title="new 和 free"></a>new 和 free</h4><p>仍然以上面的Test对象为例，代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Test *t = <span class="keyword">new</span> Test;</span><br><span class="line"><span class="built_in">free</span>(t)</span><br></pre></td></tr></table></figure>

<p>此处会产生内存泄漏，在上面，我们已经分析过，new操作符会先通过operator new分配一块内存，然后在该块内存上调用placement new即调用Test的构造函数。而在上述代码中，只是通过free函数释放了内存，但是没有调用Test的析构函数以释放Test的成员变量，从而引起内存泄漏。</p>
<p>正确的做法是：<strong>malloc和free一一对应，new和delete一一对应。</strong></p>
<h4 id="new-和-delete"><a href="#new-和-delete" class="headerlink" title="new[] 和 delete"></a>new[] 和 delete</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Test *t = <span class="keyword">new</span> Test [<span class="number">10</span>];</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  <span class="keyword">delete</span> t;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述代码中，我们通过new创建了一个Test类型的数组，然后通delete操作符删除该数组，编译并执行，输出如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in Test</span><br><span class="line">in ~Test</span><br></pre></td></tr></table></figure>

<p>从上面输出结果可以看出，调用了10次构造函数，但是只调用了一次析构函数，所以引起了内存泄漏。这是因为调用delete t释放了通过operator new[]申请的内存，即malloc申请的内存块，且只调用了t[0]对象的析构函数，t[1..9]对象的析构函数并没有被调用。</p>
<p> <strong>delete  ptr  代表用来释放内存，且只用来释放ptr指向的内存，如果ptr指向的是一个对象数组，则只会调用数组中第一个对象的destructor。</strong> </p>
<p> <strong>delete[]  ptr  用来释放ptr指向的内存，还逐一调用数组中每个对象的destructor。</strong></p>
<p> <strong>当然，对于像int/char/long/int*/struct等等简单数据类型，由于对象没有destructor，所以用delete 和delete [] 是一样的，但是如果是C++对象数组就不同了。</strong></p>
<p>正确的做法应该是<code>delete [] t;</code></p>
<hr>
<h4 id="虚析构"><a href="#虚析构" class="headerlink" title="虚析构"></a>虚析构</h4><p>如果基类的析构函数不声明为virtual，基类指针指向子类对象，析构时就不会调用子类的析构函数，可能造成内存泄漏。看下面这个例子：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Base</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Base</span>()&#123;</span><br><span class="line">    buffer_ = <span class="keyword">new</span> <span class="keyword">char</span>[<span class="number">10</span>];</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ~<span class="built_in">Base</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in Base::~Base&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">delete</span> []buffer_;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">char</span> *buffer_;</span><br><span class="line"></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> :</span> <span class="keyword">public</span> Base &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Derived</span>()&#123;&#125;</span><br><span class="line"></span><br><span class="line">  ~<span class="built_in">Derived</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in Derived::~Derived&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  Base *base = <span class="keyword">new</span> Derived;</span><br><span class="line">  <span class="keyword">delete</span> base;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面代码输出如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">in Base::~Base</span><br></pre></td></tr></table></figure>

<p>可见，上述代码并没有调用派生类Derived的析构函数，如果派生类中在堆上申请了资源，那么就会产生内存泄漏。</p>
<p><strong>为了避免因为继承导致的内存泄漏，我们需要将父类的析构函数声明为virtual</strong>，代码如下(只列了部分修改代码，其他不变):</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">virtual</span> ~<span class="built_in">Base</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in Base::~Base&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">delete</span> []buffer_;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>然后重新执行代码，输出结果如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">in Derived::~Derived</span><br><span class="line">in Base::~Base</span><br></pre></td></tr></table></figure>

<p>另外，我们复习一下在继承情况下，构造函数和析构函数的调用顺序。</p>
<p>派生类对象在创建时构造函数调用顺序：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 调用父类的构造函数</span><br><span class="line">2. 调用父类成员变量的构造函数</span><br><span class="line">3. 调用派生类本身的构造函数</span><br></pre></td></tr></table></figure>

<p>派生类对象在析构时的析构函数调用顺序：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 执行派生类自身的析构函数</span><br><span class="line">2. 执行派生类成员变量的析构函数</span><br><span class="line">3. 执行父类的析构函数</span><br></pre></td></tr></table></figure>

<p>为了避免存在继承关系时候的内存泄漏，请遵守一条规则：<strong>无论派生类有没有申请堆上的资源，请将父类的析构函数声明为virtual。</strong></p>
<hr>
<h4 id="循环引用"><a href="#循环引用" class="headerlink" title="循环引用"></a>循环引用</h4><p>在C++开发中，为了尽可能的避免内存泄漏，自C++11起引入了智能指针<code>smart pointer</code>，常见的有shared_ptr、weak_ptr以及unique_ptr等(auto_ptr已经被废弃)，其中weak_ptr是为了解决循环引用而存在，其往往与shared_ptr结合使用。</p>
<p>下面，我们看一段代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Controller</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Controller</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">  ~<span class="built_in">Controller</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in ~Controller&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">SubController</span> &#123;</span></span><br><span class="line">   <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">SubController</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">SubController</span>() &#123;</span><br><span class="line">      std::cout &lt;&lt; <span class="string">&quot;in ~SubController&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    std::shared_ptr&lt;Controller&gt; controller_;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::shared_ptr&lt;SubController&gt; sub_controller_;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">auto</span> controller = std::make_shared&lt;Controller&gt;();</span><br><span class="line">  <span class="keyword">auto</span> sub_controller = std::make_shared&lt;Controller::SubController&gt;();</span><br><span class="line"></span><br><span class="line">  controller-&gt;sub_controller_ = sub_controller;</span><br><span class="line">  sub_controller-&gt;controller_ = controller;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编译并执行上述代码，发现并没有调用Controller和SubController的析构函数，我们尝试着打印下引用计数，代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">auto</span> controller = std::make_shared&lt;Controller&gt;();</span><br><span class="line">  <span class="keyword">auto</span> sub_controller = std::make_shared&lt;Controller::SubController&gt;();</span><br><span class="line"></span><br><span class="line">  controller-&gt;sub_controller_ = sub_controller;</span><br><span class="line">  sub_controller-&gt;controller_ = controller;</span><br><span class="line"></span><br><span class="line">  std::cout &lt;&lt; <span class="string">&quot;controller use_count: &quot;</span> &lt;&lt; controller.<span class="built_in">use_count</span>() &lt;&lt; std::endl;</span><br><span class="line">  std::cout &lt;&lt; <span class="string">&quot;sub_controller use_count: &quot;</span> &lt;&lt; sub_controller.<span class="built_in">use_count</span>() &lt;&lt; std::endl;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编译并执行之后，输出如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">controller use_count: 2</span><br><span class="line">sub_controller use_count: 2</span><br></pre></td></tr></table></figure>

<p>通过上面输出可以发现，因为引用计数都是2，所以在main函数结束的时候，不会调用controller和sub_controller的析构函数，所以就出现了<strong>内存泄漏</strong>。</p>
<p>上面产生内存泄漏的原因，就是我们常说的<strong>循环引用</strong>。</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/4.png" alt="4" style="zoom:50%;">

<p>为了解决std::shared_ptr循环引用导致的内存泄漏，我们可以使用std::weak_ptr来单面去除上图中的循环：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Controller</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Controller</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">  ~<span class="built_in">Controller</span>() &#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot;in ~Controller&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">SubController</span> &#123;</span></span><br><span class="line">   <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">SubController</span>() = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">SubController</span>() &#123;</span><br><span class="line">      std::cout &lt;&lt; <span class="string">&quot;in ~SubController&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    std::weak_ptr&lt;Controller&gt; controller_;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  std::shared_ptr&lt;SubController&gt; sub_controller_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>在上述代码中，我们将SubController类中controller_的类型从std::shared_ptr变成std::weak_ptr，重新编译执行，结果如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">controller use_count: 1</span><br><span class="line">sub_controller use_count: 2</span><br><span class="line">in ~Controller</span><br><span class="line">in ~SubController</span><br></pre></td></tr></table></figure>

<p>从上面结果可以看出，controller和sub_controller均以释放，所以<code>循环引用</code>引起的内存泄漏问题，也得以解决。</p>
<img src="/2022/05/13/C-%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/5.png" alt="5" style="zoom:50%;">

<p>可能有人会问，使用std::shared_ptr可以直接访问对应的成员函数，如果是std::weak_ptr的话，怎么访问呢？我们可以使用下面的方式:</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">std::shared_ptr controller = controller_.<span class="built_in">lock</span>();</span><br></pre></td></tr></table></figure>

<p>即在子类SubController中，如果要使用controller调用其对应的函数，就可以使用上面的方式。</p>
<hr>
<h2 id="避免内存泄漏"><a href="#避免内存泄漏" class="headerlink" title="避免内存泄漏"></a>避免内存泄漏</h2><p>针对不同行为产生的内存泄漏有不同的避免方法，主要有以下几种方法：</p>
<h4 id="避免在堆上分配"><a href="#避免在堆上分配" class="headerlink" title="避免在堆上分配"></a>避免在堆上分配</h4><p>众所周知，大部分的内存泄漏都是因为在堆上分配引起的，如果我们不在堆上进行分配，就不会存在内存泄漏了(这不废话嘛)，我们可以根据具体的使用场景，如果对象可以在栈上进行分配，就在栈上进行分配，一方面栈的效率远高于堆，另一方面，还能避免内存泄漏，我们何乐而不为呢。</p>
<h4 id="手动释放"><a href="#手动释放" class="headerlink" title="手动释放"></a>手动释放</h4><ul>
<li>对于malloc函数分配的内存，在结束使用的时候，使用free函数进行释放。</li>
<li>对于new操作符创建的对象，切记使用delete来进行释放。</li>
<li>对于new []创建的对象，使用delete[]来进行释放(使用free或者delete均会造成内存泄漏)。</li>
</ul>
<h4 id="避免使用裸指针"><a href="#避免使用裸指针" class="headerlink" title="避免使用裸指针"></a>避免使用裸指针</h4><p>尽可能避免使用裸指针，除非所调用的lib库或者合作部门的接口是裸指针。</p>
<p>比如如下代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">(<span class="keyword">int</span> *ptr)</span> </span>&#123;<span class="comment">// fun 是一个接口或lib函数</span></span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;&#125;</span><br><span class="line">  <span class="keyword">int</span> a = <span class="number">1000</span>;</span><br><span class="line">  <span class="keyword">int</span> *ptr = &amp;a;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="built_in">fun</span>(ptr);</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的fun函数中，有一个参数ptr,为int *，我们需要根据上下文来分析这个指针是否需要释放，这是一种很不好的设计。</p>
<h4 id="使用STL中或者自己实现对象"><a href="#使用STL中或者自己实现对象" class="headerlink" title="使用STL中或者自己实现对象"></a>使用STL中或者自己实现对象</h4><p>在C++中，提供了相对完善且可靠的STL供我们使用，所以能用STL的尽可能的避免使用C中的编程方式，比如：</p>
<ul>
<li>使用std::string 替代char *, string类自己会进行内存管理，而且优化的相当不错。</li>
<li>使用std::vector或者std::array来替代传统的数组。</li>
<li>其它适合使用场景的对象。</li>
</ul>
<h4 id="智能指针"><a href="#智能指针" class="headerlink" title="智能指针"></a>智能指针</h4><p>自C++11开始，STL中引入了智能指针(smart pointer)来动态管理资源，针对使用场景的不同，提供了以下三种智能指针。</p>
<p><strong>unique_ptr</strong></p>
<p>unique_ptr是限制最严格的一种智能指针，用来替代之前的auto_ptr，<strong>独享被管理对象指针所有权。当unique_ptr对象被销毁时，会在其析构函数内删除关联的原始指针。</strong></p>
<p>unique_ptr对象分为以下两类：</p>
<ul>
<li><p>unique_ptr该类型的对象关联了单个Type类型的指针。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::unique_ptr&lt;Type&gt;   <span class="title">p1</span><span class="params">(<span class="keyword">new</span> Type)</span></span>; <span class="comment">// c++11</span></span><br><span class="line"><span class="keyword">auto</span> p1 = std::make_unique&lt;Type&gt;(); <span class="comment">// c++14</span></span><br></pre></td></tr></table></figure></li>
<li><p>unique_ptr&lt;Type[]&gt; 该类型的对象关联了多个Type类型指针，即一个对象数组</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::unique_ptr&lt;Type[]&gt; <span class="title">p2</span><span class="params">(<span class="keyword">new</span> Type[n]())</span></span>; <span class="comment">// c++11</span></span><br><span class="line"><span class="keyword">auto</span> p2 = std::make_unique&lt;Type[]&gt;(n); <span class="comment">// c++14</span></span><br></pre></td></tr></table></figure></li>
<li><p>不可用被复制</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">unique_ptr&lt;<span class="keyword">int</span>&gt; <span class="title">a</span><span class="params">(<span class="keyword">new</span> <span class="keyword">int</span>(<span class="number">0</span>))</span></span>;</span><br><span class="line">unique_ptr&lt;<span class="keyword">int</span>&gt; b = a;  <span class="comment">// 编译错误</span></span><br><span class="line">unique_ptr&lt;<span class="keyword">int</span>&gt; b = std::<span class="built_in">move</span>(a); <span class="comment">// 可以通过move语义进行所有权转移</span></span><br></pre></td></tr></table></figure></li>
</ul>
<p>根据使用场景，可以使用std::unique_ptr来避免内存泄漏，如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="function">unique_ptr&lt;<span class="keyword">int</span>&gt; <span class="title">a</span><span class="params">(<span class="keyword">new</span> <span class="keyword">int</span>(<span class="number">0</span>))</span></span>;</span><br><span class="line">  <span class="comment">// use a</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述fun函数结束的时候，会<strong>自动调用a的析构函数，从而释放其关联的指针。</strong></p>
<p><strong>shared_ptr</strong></p>
<p>与unique_ptr不同的是，unique_ptr是<strong>独占管理权</strong>，而shared_ptr则是<strong>共享管理权</strong>，即多个shared_ptr可以共用同一块关联对象，其内部采用的是<strong>引用计数</strong>，在拷贝的时候，引用计数+1，而在某个对象退出作用域或者释放的时候，引用计数-1，当<strong>引用计数为0的时候，会自动释放其管理的对象。</strong></p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="built_in">std</span>::<span class="built_in">shared_ptr</span>&lt;Type&gt; a; <span class="comment">// a是一个空对象</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">shared_ptr</span>&lt;Type&gt; b = <span class="built_in">std</span>::make_shared&lt;Type&gt;(); <span class="comment">// 分配资源</span></span><br><span class="line">    a = b; <span class="comment">// 此时引用计数为2</span></span><br><span class="line">    &#123;</span><br><span class="line">      <span class="built_in">std</span>::<span class="built_in">shared_ptr</span>&lt;Type&gt; c = a; <span class="comment">// 此时引用计数为3</span></span><br><span class="line">    &#125; <span class="comment">// c退出作用域，此时引用计数为2</span></span><br><span class="line">  &#125; <span class="comment">// b 退出作用域，此时引用计数为1</span></span><br><span class="line">&#125; <span class="comment">// a 退出作用域，引用计数为0，释放对象</span></span><br></pre></td></tr></table></figure>

<p><strong>weak_ptr</strong></p>
<p>weak_ptr的出现，主要是为了解决shared_ptr的<strong>循环引用</strong>，其主要是与shared_ptr一起来使用。和shared_ptr不同的地方在于，其并不会拥有资源，也就是说不能访问对象所提供的成员函数，不过，<strong>可以通过weak_ptr.lock()来产生一个拥有访问权限的shared_ptr。</strong></p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">std::weak_ptr&lt;Type&gt; a;</span><br><span class="line">&#123;</span><br><span class="line">  std::shared_ptr&lt;Type&gt; b = std::make_shared&lt;Type&gt;();</span><br><span class="line">  a = b</span><br><span class="line">&#125; <span class="comment">// b所对应的资源释放</span></span><br></pre></td></tr></table></figure>

<h4 id="RAII"><a href="#RAII" class="headerlink" title="RAII"></a>RAII</h4><p><code>RAII</code>是<code>Resource Acquisition is Initialization(资源获取即初始化)</code>的缩写，是C++语言的一种管理资源，避免泄漏的用法。</p>
<p>利用的就是<strong>C++构造的对象最终会被销毁的原则</strong>。利用C++对象生命周期的概念来控制程序的资源,比如内存,文件句柄,网络连接等。</p>
<p><strong>RAII的做法是使用一个对象，在其构造时获取对应的资源，在对象生命周期内控制对资源的访问，使之始终保持有效，最后在对象析构的时候，释放构造时获取的资源。</strong></p>
<p><strong>简单地说，就是把资源的使用限制在对象的生命周期之中，自动释放。</strong></p>
<p>举个简单的例子，通常在多线程编程的时候，都会用到std::mutex，如下代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">std::mutex mutex_;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  mutex_.<span class="built_in">lock</span>();</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (...) &#123;</span><br><span class="line">    mutex_.<span class="built_in">unlock</span>();</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  mutex_.<span class="built_in">unlock</span>()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上述代码中，如果if分支多的话，每个if分支里面都要释放锁，如果一不小心忘记释放，那么就会造成故障，为了解决这个问题，我们使用<code>RAII</code>技术，代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">std::mutex mutex_;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="function">std::lock_guard&lt;std::mutex&gt; <span class="title">guard</span><span class="params">(mutex_)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (...) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注：std::lock_guard属于C++11特性，锁管理遵循RAII习语管理资源，锁管理器在构造函数中自动绑定它的互斥体并加锁，在析构函数中解锁，大大减少了死锁的风险。</p>
</blockquote>
<p>在guard出了fun作用域的时候，会自动调用mutex_.lock()进行释放，避免了很多不必要的问题。</p>
<hr>
<h2 id="内存泄漏的定位"><a href="#内存泄漏的定位" class="headerlink" title="内存泄漏的定位"></a>内存泄漏的定位</h2><p>在发现程序存在内存泄漏后，往往需要定位泄漏点，而定位这一步往往是最困难的，所以经常为了定位泄漏点，采取各种各样的方案，甭管方案优雅与否，只要能够定位到泄漏点就是好的方法。</p>
<h4 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h4><p>这种方案的核心思想，就是<strong>在每次分配内存的时候，打印指针地址，在释放内存的时候，打印内存地址，这样在程序结束的时候，通过分配和释放的差，如果分配的条数大于释放的条数，那么基本就能确定程序存在内存泄漏</strong>，然后根据日志进行详细分析和定位。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">char</span> * <span class="title">fun</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">char</span> *p = (<span class="keyword">char</span>*)<span class="built_in">malloc</span>(<span class="number">20</span>);</span><br><span class="line">  <span class="built_in">printf</span>(<span class="string">&quot;%s, %d, address is: %p&quot;</span>, __FILE__, __LINE__, p);</span><br><span class="line">  <span class="comment">// do sth</span></span><br><span class="line">  <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="built_in">fun</span>();</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="统计"><a href="#统计" class="headerlink" title="统计"></a>统计</h4><p>统计方案可以理解为日志方案的一种特殊实现，其主要原理是<strong>在分配的时候，统计分配次数，在释放的时候，则是统计释放的次数</strong>，这样在程序结束前判断这俩值是否一致，就能判断出是否存在内存泄漏。</p>
<p>此方法可帮助跟踪已分配内存的状态。为了实现这个方案，需要创建三个自定义函数，一个用于内存分配，第二个用于内存释放，最后一个用于检查内存泄漏。代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> allocated  = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> deallocated  = <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">Memory_Allocate</span> <span class="params">(<span class="keyword">size_t</span> size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">void</span> *ptr = <span class="literal">NULL</span>;</span><br><span class="line">    ptr = <span class="built_in">malloc</span>(size);</span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">NULL</span> != ptr) &#123;</span><br><span class="line">        ++allocated;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//Log error</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ptr;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Memory_Deallocate</span> <span class="params">(<span class="keyword">void</span> *ptr)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(pvHandle != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="built_in">free</span>(ptr);</span><br><span class="line">        ++deallocated;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">Check_Memory_Leak</span><span class="params">(<span class="keyword">void</span>)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (allocated != deallocated) &#123;</span><br><span class="line">        <span class="comment">//Log error</span></span><br><span class="line">        ret = MEMORY_LEAK;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        ret = OK;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h4><p>在Linux上比较常用的内存泄漏检测工具是<strong>valgrind</strong>，所以咱们就以valgrind为工具，进行检测。</p>
<p>我们首先看一段代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span> <span class="params">(<span class="keyword">void</span>)</span></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> *buff = (<span class="keyword">char</span>*)<span class="built_in">malloc</span>(<span class="number">10</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span> <span class="params">(<span class="keyword">void</span>)</span></span>&#123;</span><br><span class="line">    <span class="built_in">func</span>(); <span class="comment">// 产生内存泄漏</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>通过<code>gcc -g leak.c -o leak</code>命令进行编译</li>
<li>执行<code>valgrind --leak-check=full ./leak</code></li>
</ul>
<p>在上述的命令执行后，会输出如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">==9652== Memcheck, a memory error detector</span><br><span class="line">==9652== Copyright (C) 2002-2017, and GNU GPL&#x27;d, by Julian Seward et al.</span><br><span class="line">==9652== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info</span><br><span class="line">==9652== Command: ./leak</span><br><span class="line">==9652==</span><br><span class="line">==9652==</span><br><span class="line">==9652== HEAP SUMMARY:</span><br><span class="line">==9652==     in use at exit: 10 bytes in 1 blocks</span><br><span class="line">==9652==   total heap usage: 1 allocs, 0 frees, 10 bytes allocated</span><br><span class="line">==9652==</span><br><span class="line">==9652== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1</span><br><span class="line">==9652==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)</span><br><span class="line">==9652==    by 0x40052E: func (leak.c:4)</span><br><span class="line">==9652==    by 0x40053D: main (leak.c:8)</span><br><span class="line">==9652==</span><br><span class="line">==9652== LEAK SUMMARY:</span><br><span class="line">==9652==    definitely lost: 10 bytes in 1 blocks</span><br><span class="line">==9652==    indirectly lost: 0 bytes in 0 blocks</span><br><span class="line">==9652==      possibly lost: 0 bytes in 0 blocks</span><br><span class="line">==9652==    still reachable: 0 bytes in 0 blocks</span><br><span class="line">==9652==         suppressed: 0 bytes in 0 blocks</span><br><span class="line">==9652==</span><br><span class="line">==9652== For lists of detected and suppressed errors, rerun with: -s</span><br><span class="line">==9652== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)</span><br></pre></td></tr></table></figure>

<p>valgrind的检测信息将内存泄漏分为如下几类：</p>
<ul>
<li><strong>definitely lost</strong>：确定产生内存泄漏</li>
<li><strong>indirectly lost</strong>：间接产生内存泄漏</li>
<li><strong>possibly lost</strong>：可能存在内存泄漏</li>
<li><strong>still reachable</strong>：即使在程序结束时候，仍然有指针在指向该块内存，常见于全局变量</li>
</ul>
<p>主要上面输出的下面几句：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">==9652==    by 0x40052E: func (leak.c:4)</span><br><span class="line">==9652==    by 0x40053D: main (leak.c:8)</span><br></pre></td></tr></table></figure>

<p>提示在main函数(leak.c的第8行)fun函数(leak.c的第四行)产生了内存泄漏，通过分析代码，原因定位，问题解决。</p>
<p>valgrind不仅可以检测内存泄漏，还有其他很强大的功能，由于本文以内存泄漏为主，所以其他的功能就不在此赘述了，有兴趣的可以通过<code>valgrind --help</code>来进行查看。</p>
<p>对于Windows下的内存泄漏检测工具，笔者推荐一款轻量级功能却非常强大的工具<strong>UMDH</strong>，如果你在Windows上进行开发，强烈推荐。</p>
<hr>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在C/C++开发过程中，内存泄漏是一个非常常见的问题，其影响相对来说远低于coredump等，所以遇到内存泄漏的时候，不用过于着急，大不了重启嘛。</p>
<p>在开发过程中遵守下面的规则，基本能90+%避免内存泄漏：</p>
<ul>
<li>良好的编程习惯，只有有malloc/new，就得有free/delete。</li>
<li>尽可能的使用智能指针，智能指针就是为了解决内存泄漏而产生。</li>
<li>使用log进行记录。</li>
<li>也是最重要的一点，<strong>谁申请，谁释放</strong>。</li>
</ul>
<p>对于malloc分配内存，分配失败的时候返回值为NULL，此时程序可以直接退出了，而对于new进行内存分配，其分配失败的时候，是抛出<code>std::bad_alloc</code>，所以为了第一时间发现问题，不要对new异常进行catch，毕竟内存都分配失败了，程序也没有运行的必要了。</p>
<p>C/C++之所以复杂、效率高，是因为其灵活性，可用直接访问操作系统API，而正因为其灵活性，就很容易出问题，团队成员必须愿意按照一定的规则来进行开发，有完整的review机制，将问题暴露在上线之前。这样才可以把精力放在业务本身，而不是查找这些问题上，有时候往往一个小问题就能消耗很久的时间去定位解决，所以，一定要有一个良好的开发习惯。</p>
<hr>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a target="_blank" rel="noopener" href="https://developers.redhat.com/blog/2021/05/05/memory-error-checking-in-c-and-c-comparing-sanitizers-and-valgrind">https://developers.redhat.com/blog/2021/05/05/memory-error-checking-in-c-and-c-comparing-sanitizers-and-valgrind</a></p>
<p><a target="_blank" rel="noopener" href="https://aticleworld.com/what-is-memory-leak-in-c-c-how-can-we-avoid/">https://aticleworld.com/what-is-memory-leak-in-c-c-how-can-we-avoid/</a></p>
<p><a target="_blank" rel="noopener" href="https://iq.opengenus.org/memory-leak-in-cpp-and-how-to-avoid-it/">https://iq.opengenus.org/memory-leak-in-cpp-and-how-to-avoid-it/</a></p>
<p><a target="_blank" rel="noopener" href="https://blog.nelhage.com/post/three-kinds-of-leaks/#type-1-unreachable-allocations">https://blog.nelhage.com/post/three-kinds-of-leaks/#type-1-unreachable-allocations</a></p>
<p><a target="_blank" rel="noopener" href="https://owasp.org/www-community/vulnerabilities/Memory_leak">https://owasp.org/www-community/vulnerabilities/Memory_leak</a></p>
<p><a target="_blank" rel="noopener" href="https://www.usna.edu/Users/cs/roche/courses/s19ic221/lab05.html">https://www.usna.edu/Users/cs/roche/courses/s19ic221/lab05.html</a></p>
<p><a target="_blank" rel="noopener" href="https://stackoverflow.com/questions/6261201/how-to-find-memory-leak-in-a-c-code-project">https://stackoverflow.com/questions/6261201/how-to-find-memory-leak-in-a-c-code-project</a></p>
<p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/pbNJA_qNZc3KpvoGgHXEIw">https://mp.weixin.qq.com/s/pbNJA_qNZc3KpvoGgHXEIw</a></p>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2022/04/18/Learn-Git-Branching/">Learn_Git_Branching</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2022-04-18</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E5%AE%9E%E7%94%A8%E6%8A%80%E6%9C%AF/">实用技术</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/%E6%8A%80%E6%9C%AF%E5%AD%A6%E4%B9%A0/">技术学习</a></span><div class="content"><h1 id="Learn-Git-Branching（主要部分）"><a href="#Learn-Git-Branching（主要部分）" class="headerlink" title="Learn Git Branching（主要部分）"></a>Learn Git Branching（主要部分）</h1><p> “Learning Git Branching” 可以说是目前为止最好的教程了，在沙盒里你能执行相应的命令，还能看到每个命令的执行情况； 通过一系列刺激的关卡挑战，逐步深入的学习 Git 的强大功能，在这个过程中你可能还会发现一些有意思的事情。</p>
<h2 id="基础篇"><a href="#基础篇" class="headerlink" title="基础篇"></a>基础篇</h2><h3 id="1-Git-Commit"><a href="#1-Git-Commit" class="headerlink" title="1 Git Commit"></a>1 Git Commit</h3><p>Git 仓库中的提交记录保存的是你的目录下<strong>所有文件的快照</strong>，就像是把整个目录复制，然后再粘贴一样，但比复制粘贴优雅许多！</p>
<p>Git 希望提交记录<strong>尽可能地轻量</strong>，因此在你每次进行提交时，它并不会盲目地复制整个目录。条件允许的情况下，它<strong>会将当前版本与仓库中的上一个版本进行对比，并把所有的差异打包到一起作为一个提交记录</strong>。</p>
<p><strong>Git 还保存了提交的历史记录。</strong>这也是为什么大多数提交记录的上面都有<strong>父节点的原因 —— 我们会在图示中用箭头来表示这种关系</strong>。对于项目组的成员来说，维护提交历史对大家都有好处。</p>
<p>可以把提交记录看作是项目的快照。提交记录非常轻量，可以快速地在这些提交记录之间切换！</p>
<h3 id="2-Git-Branch-Git-Switch"><a href="#2-Git-Branch-Git-Switch" class="headerlink" title="2 Git Branch/Git Switch"></a>2 Git Branch/Git Switch</h3><p><strong>Git 的分支也非常轻量。它们只是简单地指向某个提交纪录</strong> —— 仅此而已。所以许多 Git 爱好者传颂：</p>
<p><em><strong>早建分支！多用分支！</strong></em></p>
<p><strong>这是因为即使创建再多的分支也不会造成储存或内存上的开销，并且按逻辑分解工作到不同的分支要比维护那些特别臃肿的分支简单多了。</strong></p>
<p>在将分支和提交记录结合起来后，我们会看到两者如何协作。现在只要记住<strong>使用分支其实就相当于在说：“<em>我想基于这个提交以及它所有的父提交进行新的工作</em>。”</strong></p>
<ul>
<li>创建一个新的名为newImage的分支：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch newImage</span><br></pre></td></tr></table></figure>

<ul>
<li>然后，直接使用git commit 并不会使得newImage分支前进，而是会使当前分支（main分支）前进，使用git checkout <name> 可以让我们在提交修改之前先切换到新的分支上。</name></li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout newImage;</span><br><span class="line">git commit</span><br></pre></td></tr></table></figure>

<p><em>注意：在 Git 2.23 版本中，引入了一个名为 <code>git switch</code> 的新命令，最终会取代 <code>git checkout</code>，因为 <code>checkout</code> 作为单个命令有点超载（它承载了很多独立的功能）。可以从<a target="_blank" rel="noopener" href="https://git-scm.com/docs/git-switch">这里</a>学到<code>git switch</code> 的内容。</em></p>
<ul>
<li>有个更简洁的方式：如果你<strong>想创建一个新的分支同时切换到新创建的分支的话</strong>，可以通过 <code>git checkout -b &lt;your-branch-name&gt;</code> 来实现。</li>
</ul>
<h3 id="3-Git-merge（合并分支）"><a href="#3-Git-merge（合并分支）" class="headerlink" title="3 Git merge（合并分支）"></a>3 Git merge（合并分支）</h3><p>接下来咱们看看如何将两个分支合并到一起。就是说我们新建一个分支，在其上开发某个新功能，开发完成后再合并回主线。</p>
<p>咱们先来看一下第一种方法 —— <code>git merge</code>。<strong>在 Git 中合并两个分支时会产生一个特殊的提交记录，它有两个父节点。翻译成自然语言相当于：“我要把这两个父节点本身及它们所有的祖先都包含进来。”</strong></p>
<p>通过图示更容易理解一些:</p>
<ul>
<li>我们准备了两个分支，每个分支上各有一个独有的提交。这意味着没有一个分支包含了我们修改的所有内容。咱们通过合并这两个分支来解决这个问题。</li>
<li>我们要把 <code>bugFix</code> 合并到 <code>main</code> 里，使用：</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git merge bugFix;//当前分支为main</span><br></pre></td></tr></table></figure>

<ul>
<li>首先，<code>main</code> 现在指向了一个拥有两个父节点的提交记录。假如从 <code>main</code> 开始沿着箭头向上看，在到达起点的路上会经过所有的提交记录。这意味着 <code>main</code> 包含了对代码库的所有修改：</li>
</ul>
<img src="/2022/04/18/Learn-Git-Branching/gitmerge-16502851695131.png" alt="gitmerge" style="zoom:50%;">

<ul>
<li><p>接着，再把 <code>main</code> 分支合并到 <code>bugFix</code>：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout bugFix;</span><br><span class="line">git merge main;</span><br></pre></td></tr></table></figure>

<ul>
<li>因为 <code>main</code> 继承自 <code>bugFix</code>，Git 什么都不用做，只是简单地把 <code>bugFix</code> 移动到 <code>main</code> 所指向的那个提交记录。现在所有提交记录的颜色都一样了，这表明每一个分支都包含了代码库的所有修改！大功告成！</li>
</ul>
</li>
</ul>
<h3 id="4-Git-Rebase（重定位）"><a href="#4-Git-Rebase（重定位）" class="headerlink" title="4 Git Rebase（重定位）"></a>4 Git Rebase（重定位）</h3><p>第二种合并分支的方法是 <code>git rebase</code>。<strong>Rebase 实际上就是取出一系列的提交记录，“复制”它们，然后在另外一个地方逐个的放下去。</strong></p>
<p><strong>Rebase 的优势就是可以创造更线性的提交历史，这听上去有些难以理解。如果只允许使用 Rebase 的话，代码库的提交历史将会变得异常清晰。</strong></p>
<p>git rebase后如果跟有一个参数，则说明是将当前分支的提交复制到所指位置，如</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase base1</span><br></pre></td></tr></table></figure>

<p>git rebase后如果跟有两个参数，则说明是将第二个参数所在分支的提交复制到第一个参数的位置，如</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase o/main base2</span><br></pre></td></tr></table></figure>

<p>实验如下：</p>
<ul>
<li>还是准备了两个分支；注意当前所在的分支是 bugFix（星号标识的是当前分支）</li>
</ul>
<img src="/2022/04/18/Learn-Git-Branching/gitrebase-16502851695133.png" alt="gitrebase" style="zoom:50%;">

<ul>
<li><p>我们想要把 bugFix 分支里的工作直接移到 main 分支上。移动以后会使得两个分支的功能看起来像是按顺序开发，但实际上它们是并行开发的。</p>
</li>
<li><p>这就需要用 <code>git rebase</code> 实现此目标</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase main</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/gitrebase2-16502851695134.png" alt="gitrebase2" style="zoom:50%;"></li>
<li><p>现在 bugFix 分支上的工作在 main 的最顶端，同时我们也得到了一个更线性的提交序列。</p>
</li>
<li><p>注意，提交记录 C3 依然存在（树上那个半透明的节点），而 C3’ 是我们 Rebase 到 main 分支上的 C3 的副本。</p>
</li>
<li><p>现在唯一的问题就是 main 还没有更新，下面咱们就来更新它：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout main</span><br><span class="line">git rebase bugFix</span><br></pre></td></tr></table></figure>

<ul>
<li>由于 <code>bugFix</code> 继承自 <code>main</code>，所以 Git 只是简单的把 <code>main</code> 分支的引用向前移动了一下而已。</li>
</ul>
</li>
</ul>
<h2 id="高级篇"><a href="#高级篇" class="headerlink" title="高级篇"></a>高级篇</h2><h3 id="1-分离HEAD"><a href="#1-分离HEAD" class="headerlink" title="1 分离HEAD"></a>1 分离HEAD</h3><ul>
<li>HEAD 是一个<strong>对当前检出记录的符号引用</strong> —— 也就是指向你正在其基础上进行工作的提交记录。</li>
<li>HEAD <strong>总是指向当前分支上最近一次提交记录</strong>。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。</li>
<li>HEAD 通常情况下是指向分支名的（如 bugFix）。在你提交时，改变了 bugFix 的状态，这一变化通过 HEAD 变得可见。</li>
<li>如果想看 HEAD 指向，可以通过 <code>cat .git/HEAD</code> 查看， 如果 HEAD 指向的是一个引用，还可以用 <code>git symbolic-ref HEAD</code> 查看它的指向。</li>
</ul>
<h4 id="分离的HEAD"><a href="#分离的HEAD" class="headerlink" title="分离的HEAD"></a>分离的HEAD</h4><ul>
<li><p>分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。在命令执行之前的状态如下所示：</p>
<img src="/2022/04/18/Learn-Git-Branching/head-16502851695132.png" alt="head" style="zoom:50%;"></li>
</ul>
<p>​        HEAD -&gt; main -&gt; C1</p>
<p>​        即：HEAD 指向 main， main 指向 C1</p>
<ul>
<li><p>执行命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout C1</span><br></pre></td></tr></table></figure></li>
<li><p>现在变成了HEAD -&gt; C1  ：</p>
<img src="/2022/04/18/Learn-Git-Branching/head2-16502851695136.png" alt="head2" style="zoom:50%;"></li>
</ul>
<h3 id="2-相对引用"><a href="#2-相对引用" class="headerlink" title="2 相对引用"></a>2 相对引用</h3><ul>
<li><p>通过指定提交记录哈希值的方式在 Git 中移动不太方便。在实际应用时，并没有像本程序中这么漂亮的可视化提交树供你参考，<strong>所以你就不得不用 <code>git log</code> 来查查看提交记录的哈希值。</strong></p>
</li>
<li><p>并且哈希值在真实的 Git 世界中也会更长（注：基于 SHA-1，共 40 位）。例如前一关的介绍中的提交记录的哈希值可能是 <code>fed2da64c0efc5293610bdd892f82a58e8cbc5d8</code>。舌头都快打结了吧…</p>
</li>
<li><p>比较令人欣慰的是，Git 对哈希的处理很智能。<strong>你只需要提供能够唯一标识提交记录的前几个字符即可</strong>。因此我可以仅输入<code>fed2</code> 而不是上面的一长串字符。</p>
</li>
<li><p>正如前面所说，通过哈希值指定提交记录很不方便，所以 Git 引入了相对引用。这个就很厉害了!</p>
<p><strong>使用相对引用的话，你就可以从一个易于记忆的地方（比如 <code>bugFix</code> 分支或 <code>HEAD</code>）开始计算。</strong></p>
<p><strong>相对引用非常给力，这里我介绍两个简单的用法：</strong></p>
<ul>
<li><strong>使用 <code>^</code> 向上移动 1 个提交记录</strong></li>
<li><strong>使用 <code>~&lt;num&gt;</code> 向上移动多个提交记录，如 <code>~3</code></strong></li>
</ul>
</li>
</ul>
<h4 id="操作符"><a href="#操作符" class="headerlink" title="^操作符"></a>^操作符</h4><ul>
<li><p><strong>首先看看操作符 (^)。</strong>把这个符号加在引用名称的后面，表示让 Git 寻找指定提交记录的父提交。</p>
<ul>
<li><p>所以 <code>main^</code> 相当于“<code>main</code> 的父节点”。</p>
</li>
<li><p><code>main^^</code> 是 <code>main</code> 的第二个父节点</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout main^</span><br></pre></td></tr></table></figure>

<p>就将HEAD切换到main的父节点上了，如下图所示：</p>
<img src="/2022/04/18/Learn-Git-Branching/head3-16502851695135.png" alt="head3" style="zoom:50%;"></li>
<li><p>你也可以将 <code>HEAD</code> 作为相对引用的参照，如：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">git checkout C3;</span><br><span class="line">git checkout HEAD^;</span><br><span class="line">git checkout HEAD^;</span><br><span class="line">git checkout HEAD^;//多次使用HEAD^向上移动</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/head4-16502851695139.png" alt="head4" style="zoom:50%;"></li>
</ul>
<h4 id="“-”操作符"><a href="#“-”操作符" class="headerlink" title="“~”操作符"></a>“~”操作符</h4><ul>
<li><p>如果你想在提交树中向上移动很多步的话，敲那么多 <code>^</code> 貌似也挺烦人的，Git 当然也考虑到了这一点，于是又引入了操作符 <code>~</code>。</p>
</li>
<li><p>该操作符后面可以跟一个数字（可选，不跟数字时与 <code>^</code> 相同，向上移动一次），指定向上移动多少次。</p>
</li>
<li><p>比如使用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout HEAD~4</span><br></pre></td></tr></table></figure>

<p>效果如下：</p>
<img src="/2022/04/18/Learn-Git-Branching/head5-16502851695137.png" alt="head5" style="zoom:50%;"></li>
</ul>
<h4 id="强制修改分支位置"><a href="#强制修改分支位置" class="headerlink" title="强制修改分支位置"></a>强制修改分支位置</h4><ul>
<li><strong>使用相对引用最多的就是移动分支。</strong>可以直接使用 <code>-f</code> 选项让分支指向另一个提交。例如:</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -f main HEAD~3</span><br></pre></td></tr></table></figure>

<ul>
<li><p>上面的命令会将 main 分支强制指向 HEAD 的第 3 级父提交。</p>
</li>
<li><p>相对引用为我们提供了一种简洁的引用提交记录 <code>C1</code> 的方式， 而 <code>-f</code> 则容许我们将分支强制移动到那个位置。</p>
</li>
</ul>
<h3 id="3-撤销变更"><a href="#3-撤销变更" class="headerlink" title="3 撤销变更"></a>3 撤销变更</h3><p>在 Git 里撤销变更的方法很多。和提交一样，<strong>撤销变更由底层部分（暂存区的独立文件或者片段）和上层部分（变更到底是通过哪种方式被撤销的）组成</strong>。我们这个应用主要关注的是后者。</p>
<p>主要有两种方法用来撤销变更 —— 一是 <code>git reset</code>，还有就是 <code>git revert</code>。</p>
<h4 id="Git-Reset（本地撤销）"><a href="#Git-Reset（本地撤销）" class="headerlink" title="Git Reset（本地撤销）"></a>Git Reset（本地撤销）</h4><ul>
<li><p><strong><code>git reset</code> 通过把分支记录回退几个提交记录来实现撤销改动。</strong>你可以将这想象成“改写历史”。<code>git reset</code> 向上移动分支，原来指向的提交记录就跟从来没有提交过一样。</p>
</li>
<li><p>比如使用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset HEAD~1</span><br></pre></td></tr></table></figure>

<p>Git 就把 main 分支移回到 <code>C1</code>；现在我们的本地代码库根本就不知道有 <code>C2</code> 这个提交了。</p>
<p>（译者注：在reset后， <code>C2</code> 所做的变更还在，但是处于未加入暂存区状态。）</p>
</li>
</ul>
<h4 id="Git-Revert（远程撤销）"><a href="#Git-Revert（远程撤销）" class="headerlink" title="Git Revert（远程撤销）"></a>Git Revert（远程撤销）</h4><ul>
<li><p>虽然在你的<strong>本地分支中使用 <code>git reset</code></strong> 很方便，但是这种“改写历史”的方法对大家一起使用的远程分支是无效的哦！</p>
</li>
<li><p>**为了撤销更改并分享给别人，我们需要使用 <code>git revert</code>**。来看演示：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git revert HEAD</span><br></pre></td></tr></table></figure>

<p>效果如下：</p>
<img src="/2022/04/18/Learn-Git-Branching/revert-16502851695138.png" alt="revert" style="zoom:50%;"></li>
<li><p>注意，使用git revert后，在我们要撤销的提交记录后面多了一个新提交！这是因为新提交记录 <code>C2&#39;</code> 引入了<strong>更改</strong> —— 这些更改刚好是用来撤销 <code>C2</code> 这个提交的。也就是说 <code>C2&#39;</code> 的状态与 <code>C1</code> 是相同的。</p>
</li>
<li><p>revert 之后就可以把你的更改推送到远程仓库与别人分享啦。</p>
</li>
</ul>
<h2 id="移动提交记录"><a href="#移动提交记录" class="headerlink" title="移动提交记录"></a>移动提交记录</h2><p>到现在我们已经学习了 Git 的基础知识 —— 提交、分支以及在提交树上移动。 这些概念涵盖了 Git 90% 的功能，同样也足够满足开发者的日常需求</p>
<p>然而, <strong>剩余的 10% 在处理复杂的工作流时(或者当你陷入困惑时）可能就显得尤为重要了</strong>。接下来要讨论的这个话题是“整理提交记录” —— 开发人员有时会说“我想要把这个提交放到这里, 那个提交放到刚才那个提交的后面”, 而接下来就讲的就是它的实现方式，非常清晰、灵活，还很生动。</p>
<h3 id="1-Git-Cherry-pick（抓取任意提交）"><a href="#1-Git-Cherry-pick（抓取任意提交）" class="headerlink" title="1 Git Cherry-pick（抓取任意提交）"></a>1 Git Cherry-pick（抓取任意提交）</h3><p>本系列的第一个命令是 <code>git cherry-pick</code>, 命令形式为:</p>
<ul>
<li><code>git cherry-pick &lt;提交号&gt;...</code></li>
</ul>
<p><strong>如果你想将一些提交复制到当前所在的位置（<code>HEAD</code>）下面的话， Cherry-pick 是最直接的方式了。</strong></p>
<p>咱们还是通过例子来看一下！ </p>
<ul>
<li><p>这里有一个仓库, 我们想将 <code>side</code> 分支上的工作复制到 <code>main</code> 分支，你立刻想到了之前学过的 <code>rebase</code> 了吧？但是咱们还是看看 <code>cherry-pick</code> 有什么本领吧。</p>
</li>
<li><img src="/2022/04/18/Learn-Git-Branching/cherry-165028516951311.png" alt="cherry" style="zoom:50%;"></li>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git cherry-pick C2 C4</span><br></pre></td></tr></table></figure>

<p>我们只需要提交记录 <code>C2</code> 和 <code>C4</code>，上面这条命令让 Git 将它们抓过来放到当前分支下了。</p>
<img src="/2022/04/18/Learn-Git-Branching/rebase3-165028516951310.png" alt="rebase3" style="zoom:50%;"></li>
</ul>
<h3 id="2-交互式的-rebase"><a href="#2-交互式的-rebase" class="headerlink" title="2 交互式的 rebase"></a>2 交互式的 rebase</h3><ul>
<li><p>当你知道你所需要的提交记录（<strong>并且</strong>还知道这些提交记录的哈希值）时, 用 cherry-pick 再好不过了 —— 没有比这更简单的方式了。</p>
</li>
<li><p>但是如果你不清楚你想要的提交记录的哈希值呢? 幸好 Git 帮你想到了这一点, 我们可以<strong>利用交互式的 rebase —— 如果你想从一系列的提交记录中找到想要的记录, 这就是最好的方法了</strong>。</p>
</li>
<li><p><strong>交互式 rebase 指的是使用带参数 <code>--interactive</code> 的 rebase 命 令, 简写为 <code>-i</code></strong></p>
</li>
<li><p><strong>如果你在命令后增加了这个选项, Git 会打开一个 UI 界面 并列出将要被复制到目标分支的备选提交记录，它还会显示每个提交记录的哈希值和提交说明，提交说明有助于你理解这个提交进行了哪些更改。</strong></p>
</li>
<li><p>在实际使用时，所谓的 UI 窗口一般会在文本编辑器 —— 如 Vim —— 中打开一个文件。</p>
</li>
<li><p>当 rebase UI界面打开时, 你能做3件事:</p>
<ul>
<li>调整提交记录的顺序（通过鼠标拖放来完成）</li>
<li>删除你不想要的提交（通过切换 <code>pick</code> 的状态来完成，关闭就意味着你不想要这个提交记录）</li>
<li>合并提交。简而言之，它允许你把多个提交记录合并成一个。</li>
</ul>
</li>
</ul>
<p>接下来咱们看个实例：</p>
<ul>
<li><img src="/2022/04/18/Learn-Git-Branching/rebase-165028516951312.png" alt="rebase" style="zoom:50%;"></li>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase -i HEAD~4</span><br></pre></td></tr></table></figure>

<p>会出现一个交互对话框。对提交记录做个排序（当然你也可以删除某些提交）：</p>
<img src="/2022/04/18/Learn-Git-Branching/rebase2-165028516951313.png" alt="rebase2" style="zoom:50%;"></li>
<li><p>Git将严格按照你在对话框中指定的方式进行了复制。</p>
</li>
</ul>
<h2 id="杂项（技巧与贴士）"><a href="#杂项（技巧与贴士）" class="headerlink" title="杂项（技巧与贴士）"></a>杂项（技巧与贴士）</h2><h3 id="1-本地栈式提交"><a href="#1-本地栈式提交" class="headerlink" title="1 本地栈式提交"></a>1 本地栈式提交</h3><ul>
<li><p>来看一个在开发中经常会遇到的情况：我正在解决某个特别棘手的 Bug，为了便于调试而在代码中添加了一些调试命令并向控制台打印了一些信息。</p>
</li>
<li><p><strong>这些调试和打印语句都在它们各自的提交记录里。</strong>最后我终于找到了造成这个 Bug 的根本原因，解决掉以后觉得沾沾自喜！</p>
</li>
<li><p><strong>最后就差把 <code>bugFix</code> 分支里的工作合并回 <code>main</code> 分支了。</strong>你可以选择通过 fast-forward 快速合并到 <code>main</code> 分支上，但这样的话 <code>main</code> 分支就会包含我这些调试语句了。你肯定不想这样，应该还有更好的方式……</p>
</li>
<li><p>实际我们只要让 Git 复制解决问题的那一个提交记录就可以了。跟之前我们在“整理提交记录”中学到的一样，我们可以使用</p>
</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git rebase -i</span><br><span class="line">git cherry-pick</span><br></pre></td></tr></table></figure>

<p>​       来达到目的。</p>
<h3 id="2-提交的技巧"><a href="#2-提交的技巧" class="headerlink" title="2 提交的技巧"></a>2 提交的技巧</h3><ul>
<li>接下来这种情况也是很常见的：你之前在 <code>newImage</code> 分支上进行了一次提交，然后又基于它创建了 <code>caption</code> 分支，然后又提交了一次。</li>
<li>此时你想对某个以前的提交记录进行一些小小的调整。比如设计师想修改一下 <code>newImage</code> 中图片的分辨率，尽管那个提交记录并不是最新的了。</li>
<li>我们可以通过下面的方法来克服困难：<ul>
<li>先用 <code>git rebase -i</code> 将提交重新排序，然后把我们想要修改的提交记录挪到最前</li>
<li>然后用 <code>git commit --amend</code> 来进行一些小修改</li>
<li>接着再用 <code>git rebase -i</code> 来将他们调回原来的顺序</li>
<li>最后我们把 main 移到修改的最前端（用你自己喜欢的方法），就大功告成啦！</li>
</ul>
</li>
</ul>
<h3 id="3-提交的技巧2"><a href="#3-提交的技巧2" class="headerlink" title="3 提交的技巧2"></a>3 提交的技巧2</h3><ul>
<li><p>正如你在上一节所见到的，我们可以使用 <code>rebase -i</code> 对提交记录进行重新排序。只要把我们想要的提交记录挪到最前端，我们就可以很轻松的用 <code>--amend</code> 修改它，然后把它们重新排成我们想要的顺序。</p>
</li>
<li><p>但这样做就唯一的问题就是要进行两次排序，而这<strong>有可能造成由 rebase 而导致的冲突</strong>。下面还是看看 <code>git cherry-pick</code> 是怎么做的吧。</p>
</li>
<li><p><strong>要在心里牢记 cherry-pick 可以将提交树上任何地方的提交记录取过来追加到 HEAD 上（只要不是 HEAD 上游的提交就没问题）。</strong></p>
</li>
</ul>
<h3 id="4-Git-Tags（打标签）"><a href="#4-Git-Tags（打标签）" class="headerlink" title="4 Git Tags（打标签）"></a>4 Git Tags（打标签）</h3><ul>
<li>相信通过前面课程的学习你已经发现了：分支很容易被人为移动，并且当有新的提交时，它也会移动。分支很容易被改变，大部分分支还只是临时的，并且还一直在变。</li>
<li>你可能会问了：有没有什么可以<strong>永远</strong>指向某个提交记录的标识呢，比如软件发布新的大版本，或者是修正一些重要的 Bug 或是增加了某些新特性，有没有比分支更好的可以永远指向这些提交的方法呢？</li>
<li>当然有了！那就是<strong>Git tag，它们可以（在某种程度上 —— 因为标签可以被删除后重新在另外一个位置创建同名的标签）永久地将某个特定的提交命名为里程碑，然后就可以像分支一样引用了</strong>。</li>
<li>更难得的是，它们并不会随着新的提交而移动。你也不能切换到某个标签上面进行修改提交，<strong>它就像是提交树上的一个锚点，标识了某个特定的位置。</strong></li>
</ul>
<p>咱们来看看标签到底是什么样：</p>
<ul>
<li><p>咱们先建立一个标签，指向提交记录 <code>C1</code>，表示这是我们 1.0 版本。</p>
<ul>
<li><p>使用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git tag v1 C1</span><br></pre></td></tr></table></figure>

<p>将这个标签命名为 <code>v1</code>，并且明确地让它指向提交记录 <code>C1</code>，如果你不指定提交记录，Git 会用 <code>HEAD</code> 所指向的位置。</p>
 <img src="/2022/04/18/Learn-Git-Branching/tag-165028516951314.png" alt="tag" style="zoom:50%;"></li>
</ul>
</li>
</ul>
<h3 id="5-Git-Describe（找最近版本）"><a href="#5-Git-Describe（找最近版本）" class="headerlink" title="5 Git Describe（找最近版本）"></a>5 Git Describe（找最近版本）</h3><ul>
<li><p>由于<strong>标签在代码库中起着“锚点”的作用</strong>，Git 还为此专门设计了一个命令用来<strong>描述</strong>离你最近的锚点（也就是标签），它就是 <code>git describe</code>！</p>
</li>
<li><p><strong>Git Describe 能帮你在提交历史中移动了多次以后找到方向</strong>；当你用 <code>git bisect</code>（一个查找产生 Bug 的提交记录的指令）找到某个提交记录时，或者是当你坐在你那刚刚度假回来的同事的电脑前时， 可能会用到这个命令。</p>
</li>
<li><p><code>git describe</code> 的语法是：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git describe &lt;ref&gt;</span><br></pre></td></tr></table></figure>

<p><code>&lt;ref&gt;</code> 可以是任何能被 Git 识别成提交记录的引用，如果你没有指定的话，Git 会以你目前所检出的位置（<code>HEAD</code>）。</p>
<p>它输出的结果是这样的：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;tag&gt;_&lt;numCommits&gt;_g&lt;hash&gt;</span><br></pre></td></tr></table></figure>

<p><code>tag</code> 表示的是离 <code>ref</code> 最近的标签， <code>numCommits</code> 是表示这个 <code>ref</code> 与 <code>tag</code> 相差有多少个提交记录， <code>hash</code> 表示的是你所给定的 <code>ref</code> 所表示的提交记录哈希值的前几位。</p>
<p>当 <code>ref</code> 提交记录上有某个标签时，则只输出标签名称。</p>
</li>
<li><p>让我们来看一个例子，对于下面的提交树：</p>
<img src="/2022/04/18/Learn-Git-Branching/describe-165028516951315.png" alt="describe" style="zoom:50%;"></li>
</ul>
<p><code>git describe main</code> 会输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">v1_2_gC2</span><br></pre></td></tr></table></figure>

<p><code>git describe side</code> 会输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">v2_1_gC4</span><br></pre></td></tr></table></figure>

<h2 id="高级话题"><a href="#高级话题" class="headerlink" title="高级话题"></a>高级话题</h2><h3 id="1-多次Rebase"><a href="#1-多次Rebase" class="headerlink" title="1 多次Rebase"></a>1 多次Rebase</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git rebase another main</span><br></pre></td></tr></table></figure>

<p>表示将main分支rebase 到another分支。</p>
<h3 id="2-两个父节点"><a href="#2-两个父节点" class="headerlink" title="2 两个父节点"></a>2 两个父节点</h3><ul>
<li><p>操作符 <code>^</code> 与 <code>~</code> 符一样，后面也可以跟一个数字。</p>
</li>
<li><p>但是该操作符后面的数字与 <code>~</code> 后面的不同，并不是用来指定向上返回几代，而是<strong>指定合并提交记录的某个父提交。</strong>还记得前面提到过的一个合并提交有两个父提交吧，所以遇到这样的节点时该选择哪条路径就不是很清晰了。</p>
</li>
<li><p>Git 默认选择合并提交的“第一个”父提交，在操作符 <code>^</code> 后跟一个数字可以改变这一默认行为。</p>
</li>
</ul>
<p>举个例子：</p>
<ul>
<li><p>这里有一个合并提交记录。如果不加数字修改符直接检出 <code>main^</code>，会回到第一个父提交记录。(<em>在我们的图示中，第一个父提交记录是指合并提交记录正上方的那个提交记录。</em>)</p>
<img src="/2022/04/18/Learn-Git-Branching/twofather-165028516951316.png" alt="twofather" style="zoom:50%;"></li>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout main^</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/twofather2-165028516951317.png" alt="twofather2" style="zoom:50%;"></li>
<li><p>使用命令</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout main^2</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/twofather3-165028516951318.png" alt="twofather3" style="zoom:50%;"></li>
<li><p>这样就回到了另一个父提交上。</p>
</li>
<li><p>使用 <code>^</code> 和 <code>~</code> 可以自由地在提交树中移动，非常给力：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git checkout HEAD~;</span><br><span class="line">git checkout HEAD^2;//选择第二个父分支</span><br><span class="line">git checkout HEAD~2//在当前分支上向上移动两次提交</span><br></pre></td></tr></table></figure></li>
<li><p>这些操作符还支持链式操作</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout HEAD~^2~2</span><br></pre></td></tr></table></figure>

<p>效果同上。</p>
</li>
</ul>
<h1 id="Learn-Git-Branching（远程部分）"><a href="#Learn-Git-Branching（远程部分）" class="headerlink" title="Learn Git Branching（远程部分）"></a>Learn Git Branching（远程部分）</h1><p>远程仓库只是你的仓库在另个一台计算机上的拷贝。你可以通过因特网与这台计算机通信 —— 也就是增加或是获取提交记录。</p>
<p>话虽如此, 远程仓库却有一系列强大的特性</p>
<ul>
<li>首先也是最重要的的点, 远程仓库是一个强大的备份。本地仓库也有恢复文件到指定版本的能力, 但所有的信息都是保存在本地的。有了远程仓库以后，即使丢失了本地所有数据, 你仍可以通过远程仓库拿回你丢失的数据。</li>
<li>还有就是, 远程让代码社交化了! 既然你的项目被托管到别的地方了, 你的朋友可以更容易地为你的项目做贡献(或者拉取最新的变更)</li>
</ul>
<p>现在用网站来对远程仓库进行可视化操作变得越发流行了(像 <a target="_blank" rel="noopener" href="https://github.com/">GitHub</a> 或 <a target="_blank" rel="noopener" href="http://phabricator.org/">Phabricator</a>), 但远程仓库<strong>永远</strong>是这些工具的顶梁柱, 因此理解其概念非常的重要!</p>
<h2 id="Push-amp-Pull"><a href="#Push-amp-Pull" class="headerlink" title="Push&amp;Pull"></a>Push&amp;Pull</h2><h3 id="1-Git-clone"><a href="#1-Git-clone" class="headerlink" title="1 Git clone"></a>1 Git clone</h3><ul>
<li>从技术上来讲，<code>git clone</code> 命令在真实的环境下的作用是在<strong>本地</strong>创建一个远程仓库的拷贝（比如从 github.com）。 （当然，它也可以在远程创建一个你本地仓库的副本。）</li>
</ul>
<h3 id="2-远程分支"><a href="#2-远程分支" class="headerlink" title="2 远程分支"></a>2 远程分支</h3><p><strong>远程分支指的是在本地git中的与远程仓库关联的分支。</strong></p>
<ul>
<li>使用git clone后，第一个事就是在我们的本地仓库多了一个名为 <code>o/main</code> 的分支, 这种类型的分支就叫<strong>远程</strong>分支。由于远程分支的特性导致其拥有一些特殊属性。</li>
<li>远程分支反映了远程仓库(在你上次和它通信时)的<strong>状态</strong>。这会有助于你理解本地的工作与公共工作的差别 —— 这是你与别人分享工作成果前至关重要的一步.</li>
<li>远程分支有一个特别的属性，在你检出时自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, （更新了远程分支之后）再用远程分享你的工作成果。</li>
</ul>
<p><strong>为什么有 <code>o/</code>？</strong></p>
<ul>
<li>你可能想问这些远程分支的前面的 <code>o/</code> 是什么意思呢？好吧, 远程分支有一个命名规范 —— 它们的格式是:</li>
</ul>
<p>​        <code>&lt;remote name&gt;/&lt;branch name&gt;</code></p>
<ul>
<li>因此，如果你看到一个名为 <code>o/main</code> 的分支，那么这个分支就叫 <code>main</code>，远程仓库的名称就是 <code>o</code>。</li>
<li>大多数的开发人员会将它们主要的远程仓库命名为 <code>origin</code>，并不是 <code>o</code>。这是因为当你用 <code>git clone</code> 某个仓库时，Git 已经帮你把远程仓库的名称设置为 <code>origin</code> 了</li>
</ul>
<p>不过 <code>origin</code> 对于我们的 UI 来说太长了，因此不得不使用简写 <code>o</code> 但是要记住, 当你使用真正的 Git 时, 你的远程仓库默认为 <code>origin</code>!</p>
<p><strong>如果检出远程分支会怎么样呢？</strong></p>
<ul>
<li><p>使用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout origin/main;</span><br><span class="line">git commit;</span><br></pre></td></tr></table></figure></li>
<li><p>GIt变成了分离 HEAD 状态，当添加新的提交时 <code>o/main</code> 也不会更新。这是因为 <code>o/main</code> 只有在远程仓库中相应的分支更新了以后才会更新。</p>
</li>
</ul>
<h3 id="3-Git-Fetch"><a href="#3-Git-Fetch" class="headerlink" title="3 Git Fetch"></a>3 Git Fetch</h3><ul>
<li><p><strong>Git 远程仓库相当的操作实际可以归纳为两点：向远程仓库传输数据以及从远程仓库获取数据。</strong>既然我们能与远程仓库同步，那么就可以分享任何能被 Git 管理的更新（因此可以分享代码、文件、想法、情书等等）。</p>
</li>
<li><p>本节课我们将学习<strong>如何从远程仓库获取数据</strong> —— 命令如其名，它就是 <code>git fetch</code>。</p>
</li>
</ul>
<hr>
<p><strong>git fetch 做了些什么</strong></p>
<ul>
<li><p><code>git fetch</code> 完成了仅有的但是很重要的两步:</p>
<ul>
<li><p>从远程仓库下载本地仓库中缺失的提交记录</p>
</li>
<li><p>更新远程分支指针(如 <code>o/main</code>)</p>
</li>
</ul>
</li>
<li><p><strong><code>git fetch</code> 实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。</strong></p>
</li>
<li><p>如果你还记得上一节课程中我们说过的，远程分支反映了远程仓库在你<strong>最后一次与它通信时</strong>的状态，<code>git fetch</code> 就是你与远程仓库通信的方式了！希望我说的够明白了，你已经了解 <code>git fetch</code> 与远程分支之间的关系了吧。</p>
</li>
<li><p><code>git fetch</code> 通常通过互联网（使用 <code>http://</code> 或 <code>git://</code> 协议) 与远程仓库通信。</p>
</li>
</ul>
<p><strong>git fetch 不会做的事</strong></p>
<ul>
<li><strong><code>git fetch</code> 并不会改变你本地仓库的状态。它不会更新你的 <code>main</code> 分支，也不会修改你磁盘上的文件。</strong></li>
<li>理解这一点很重要，因为许多开发人员误以为执行了 <code>git fetch</code> 以后，他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来，但是<strong>并没有</strong>修改你本地的文件。</li>
</ul>
<p>所以, 你可以将 <code>git fetch</code> 的理解为单纯的下载操作。</p>
<h3 id="4-Git-Pull"><a href="#4-Git-Pull" class="headerlink" title="4 Git Pull"></a>4 Git Pull</h3><p>当远程分支中有新的提交时，你可以像合并本地分支那样来合并远程分支。也就是说就是你可以执行以下命令:</p>
<ul>
<li><code>git cherry-pick o/main</code></li>
<li><code>git rebase o/main</code></li>
<li><code>git merge o/main</code></li>
<li>等等</li>
</ul>
<p>实际上，由于先抓取更新再合并到本地分支这个流程很常用，因此 Git 提供了一个专门的命令来完成这两个操作。它就是我们要讲的 <code>git pull</code>。</p>
<p>也就是说git pull做的就是抓取更新合并的过程，即 <code>git pull</code> 就是 git fetch 和 git merge 的缩写。</p>
<h3 id="5-团队合作"><a href="#5-团队合作" class="headerlink" title="5 团队合作"></a>5 团队合作</h3><p>团队合作中，你的同事、朋友、合作伙伴更新了远程仓库，有可能是某个特定的分支，或是几个提交记录。</p>
<p>比如，你的同事在远程分支上提交了两次，这时，但是你自己的本地分支上也做一些提交，这就需要拉取远程仓库的变更。也就是git pull，这样的操作会让你的本地分支抓取远程分支的更新并合并。</p>
<h3 id="6-Git-Push"><a href="#6-Git-Push" class="headerlink" title="6 Git Push"></a>6 Git Push</h3><p>上传自己分享内容与下载他人的分享刚好相反，那与 <code>git pull</code> 相反的命令是什么呢？<code>git push</code>！</p>
<p><code>git push</code> 负责将<strong>你的</strong>变更上传到指定的远程仓库，并在远程仓库上合并你的新提交记录。一旦 <code>git push</code> 完成, 你的朋友们就可以从这个远程仓库下载你分享的成果了！</p>
<p>你可以将 <code>git push</code> 想象成发布你成果的命令。</p>
<p><em>注意 —— <code>git push</code> 不带任何参数时的行为与 Git 的一个名为 <code>push.default</code> 的配置有关。它的默认值取决于你正使用的 Git 的版本，在你的项目中进行推送之前，最好检查一下这个配置。</em></p>
<h3 id="7-偏离的工作"><a href="#7-偏离的工作" class="headerlink" title="7 偏离的工作"></a>7 偏离的工作</h3><p>假设你周一克隆了一个仓库，然后开始研发某个新功能。到周五时，你新功能开发测试完毕，可以发布了。但是 —— 天啊！你的同事这周写了一堆代码，还改了许多你的功能中使用的 API，这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了，因此你的工作就变成了基于项目<strong>旧版</strong>的代码，与远程仓库最新的代码不匹配了。</p>
<p>这种情况下, <code>git push</code> 就不知道该如何操作了。如果你执行 <code>git push</code>，Git 应该让远程仓库回到星期一那天的状态吗？还是直接在新代码的基础上添加你的代码，亦或由于你的提交已经过时而直接忽略你的提交？</p>
<p>因为这情况（历史偏离）有许多的不确定性，Git 是不会允许你 <code>push</code> 变更的。实际上它会<strong>强制你先合并远程最新的代码，然后才能分享你的工作。</strong></p>
<hr>
<p><strong>那该如何解决这个问题呢？很简单，你需要做的就是使你的工作基于最新的远程分支。</strong></p>
<p>有许多方法做到这一点呢，不过最直接的方法就是通过 rebase 调整你的工作。</p>
<p>如下例子：</p>
<img src="/2022/04/18/Learn-Git-Branching/偏离的push-165028516951320.png" alt="偏离的push" style="zoom:60%;">

<ul>
<li><p>在push前做rebase：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git fetch;</span><br><span class="line">git rebase o/main;</span><br><span class="line">git push</span><br></pre></td></tr></table></figure>

<img src="/2022/04/18/Learn-Git-Branching/偏离的push2-165028516951319.png" alt="偏离的push2" style="zoom:60%;"></li>
<li><p>我们用 <code>git fetch</code> 更新了本地仓库中的远程分支，然后用 rebase 将我们的工作移动到最新的提交记录下，最后再用 <code>git push</code> 推送到远程仓库。</p>
</li>
</ul>
<p>还有其它的方法可以在远程仓库变更了以后更新我的工作吗? 当然有，我们还可以使用 <code>merge</code></p>
<ul>
<li><p>尽管 <code>git merge</code> 不会移动你的工作（它会创建新的合并提交），但是它会告诉 Git 你已经合并了远程仓库的所有变更。这是因为远程分支现在是你本地分支的祖先，也就是说你的提交已经包含了远程分支的所有变化。</p>
</li>
<li><p>看下演示（也是由上上图得来）：</p>
<img src="/2022/04/18/Learn-Git-Branching/偏离的push3-165028516951321.png" alt="偏离的push3" style="zoom:60%;">

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git fetch;</span><br><span class="line">git merge o/main;</span><br><span class="line">git push;</span><br></pre></td></tr></table></figure></li>
<li><p>我们用 <code>git fetch</code> 更新了本地仓库中的远程分支，然后<strong>合并</strong>了新变更到我们的本地分支（为了包含远程仓库的变更），最后我们用 <code>git push</code> 把工作推送到远程仓库</p>
</li>
</ul>
<p>很好！但是要敲那么多命令，有没有更简单一点的？</p>
<ul>
<li>当然 —— 前面已经介绍过 <code>git pull</code> 就是 fetch 和 merge 的简写，类似的 <strong><code>git pull --rebase</code> 就是 fetch 和 rebase 的简写！</strong></li>
</ul>
<h3 id="8-锁定的Main（Locked-Main）"><a href="#8-锁定的Main（Locked-Main）" class="headerlink" title="8 锁定的Main（Locked Main）"></a>8 锁定的Main（Locked Main）</h3><ul>
<li>如果你是在一个大的合作团队中工作, 很可能是main被锁定了, 需要一些Pull Request流程来合并修改。如果你直接提交(commit)到本地main, 然后试图推送(push)修改, 你将会收到这样类似的信息:</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">! [远程服务器拒绝] main -&gt; main (TF402455: 不允许推送(push)这个分支; 你必须使用pull request来更新这个分支.)</span><br></pre></td></tr></table></figure>

<p><strong>为什么会被拒绝?</strong></p>
<ul>
<li><p>远程服务器拒绝直接推送(push)提交到main, 因为策略配置要求 pull requests 来提交更新。</p>
</li>
<li><p>你应该按照流程,新建一个分支, 推送(push)这个分支并申请pull request,但是你忘记并直接提交给了main.现在你卡住并且无法推送你的更新。</p>
</li>
</ul>
<p><strong>解决办法：</strong></p>
<ul>
<li>新建一个分支feature, 推送到远程服务器. 然后reset你的main分支和远程服务器保持一致, 否则下次你pull并且他人的提交和你冲突的时候就会有问题.</li>
</ul>
<h2 id="Git-远程仓库高级操作"><a href="#Git-远程仓库高级操作" class="headerlink" title="Git 远程仓库高级操作"></a>Git 远程仓库高级操作</h2><h3 id="1、推送主分支"><a href="#1、推送主分支" class="headerlink" title="1、推送主分支"></a>1、推送主分支</h3><p>在大型项目中开发人员通常会在（从 <code>main</code> 上分出来的）特性分支上工作，工作完成后只做一次集成。但是有些开发人员只在 main 上做 push、pull —— 这样的话 main 总是最新的，始终与远程分支 (o/main) 保持一致。</p>
<p>对于接下来这个工作流，我们集成了两个步骤：</p>
<ul>
<li>将特性分支集成到 <code>main</code> 上</li>
<li>推送并更新远程分支</li>
</ul>
<p>例子：</p>
<ul>
<li>这里共有三个特性分支 —— <code>side1</code> <code>side2</code> 和 <code>side3</code></li>
<li>我需要将这三分支按顺序推送到远程仓库</li>
<li>因为远程仓库已经被更新过了，所以我们还要把那些工作合并过来</li>
</ul>
<p>目前状态：</p>
<img src="/2022/04/18/Learn-Git-Branching/side1-165028516951422.png" alt="side1" style="zoom:60%;">

<p>目标状态：</p>
<img src="/2022/04/18/Learn-Git-Branching/side2-165028516951423.png" alt="side2" style="zoom: 50%;">

<p>具体操作：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git fetch</span><br><span class="line">git rebase o/main side1</span><br><span class="line">git rebase side1 side2</span><br><span class="line">git rebase side2 side3</span><br><span class="line">git rebase side3 main</span><br><span class="line">git push</span><br></pre></td></tr></table></figure>

<h3 id="2、合并远程仓库"><a href="#2、合并远程仓库" class="headerlink" title="2、合并远程仓库"></a>2、合并远程仓库</h3><p>为了 push 新变更到远程仓库，你要做的就是<strong>包含</strong>远程仓库中最新变更。意思就是只要你的本地分支包含了远程分支（如 <code>o/main</code>）中的最新变更就可以了，至于具体是用 rebase 还是 merge，并没有限制。</p>
<p>在开发社区里，有许多关于 merge 与 rebase 的讨论。以下是关于 rebase 的优缺点：</p>
<p>优点:</p>
<ul>
<li>Rebase 使你的提交树变得很干净, 所有的提交都在一条线上</li>
</ul>
<p>缺点:</p>
<ul>
<li>Rebase 修改了提交树的历史</li>
</ul>
<p>比如, 提交 C1 可以被 rebase 到 C3 之后。这看起来 C1 中的工作是在 C3 之后进行的，但实际上是在 C3 之前。</p>
<p>一些开发人员喜欢保留提交历史，因此更偏爱 merge。而其他人（比如我自己）可能更喜欢干净的提交树，于是偏爱 rebase。仁者见仁，智者见智。 </p>
<p>上面关卡中的问题如果用merge来解决，则方案如下:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git checkout main</span><br><span class="line">git pull</span><br><span class="line">git merge side1</span><br><span class="line">git merge side2</span><br><span class="line">git merge side3</span><br><span class="line">git push</span><br></pre></td></tr></table></figure>

<p>结果如下：</p>
<img src="/2022/04/18/Learn-Git-Branching/mergepush-165028516951424.png" alt="mergepush" style="zoom:60%;">

<h3 id="3、远程跟踪分支"><a href="#3、远程跟踪分支" class="headerlink" title="3、远程跟踪分支"></a>3、远程跟踪分支</h3><p>在前几节课程中有件事儿挺神奇的，Git 好像知道 <code>main</code> 与 <code>o/main</code> 是相关的。当然这些分支的名字是相似的，可能会让你觉得是依此将远程分支 main 和本地的 main 分支进行了关联。这种关联在以下两种情况下可以清楚地得到展示：</p>
<ul>
<li>pull 操作时, 提交记录会被先下载到 o/main 上，之后再合并到本地的 main 分支。隐含的合并目标由这个关联确定的。</li>
<li>push 操作时, 我们把工作从 <code>main</code> 推到远程仓库中的 <code>main</code> 分支(同时会更新远程分支 <code>o/main</code>) 。这个推送的目的地也是由这种关联确定的！</li>
</ul>
<p>直接了当地讲，**<code>main</code> 和 <code>o/main</code> 的关联关系就是由分支的“remote tracking”属性决定的。<code>main</code> 被设定为跟踪 <code>o/main</code> —— 这意味着为 <code>main</code> 分支指定了推送的目的地以及拉取后合并的目标。**</p>
<p>你可能想知道 <code>main</code> 分支上这个属性是怎么被设定的，你并没有用任何命令指定过这个属性呀！好吧, <strong>当你克隆仓库的时候, Git 就自动帮你把这个属性设置好了。</strong></p>
<p><strong>当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支（比如 <code>o/main</code>）。然后再创建一个跟踪远程仓库中活动分支的本地分支，默认情况下这个本地分支会被命名为 <code>main</code>。</strong></p>
<p>克隆完成后，你会得到一个本地分支（如果没有这个本地分支的话，你的目录就是“空白”的），但是可以查看远程仓库中所有的分支（如果你好奇心很强的话）。这样做对于本地仓库和远程仓库来说，都是最佳选择。</p>
<p>这也解释了为什么会在克隆的时候会看到下面的输出：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">local branch &quot;main&quot; set to track remote branch &quot;o/main&quot;</span><br></pre></td></tr></table></figure>

<p><strong>我能自己指定这个属性吗？</strong></p>
<p>当然可以啦！你可以让任意分支跟踪 <code>o/main</code>, 然后该分支会像 <code>main</code> 分支一样得到隐含的 push 目的地以及 merge 的目标。 这意味着你可以在分支 <code>totallyNotMain</code> 上执行 <code>git push</code>，将工作推送到远程仓库的 <code>main</code> 分支上。</p>
<p>有两种方法设置这个属性，</p>
<ul>
<li>第一种就是通过远程分支检出一个新的分支，执行:</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b totallyNotMain o/main</span><br></pre></td></tr></table></figure>

<p>就可以创建一个名为 <code>totallyNotMain</code> 的分支，它跟踪远程分支 <code>o/main</code>。</p>
<p>这样pull和push的远程分支o/main跟踪的本地分支就是totallyNotMain了</p>
<ul>
<li>第二种方法</li>
</ul>
<p>另一种设置远程追踪分支的方法就是使用：<code>git branch -u</code> 命令，执行：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -u o/main foo</span><br></pre></td></tr></table></figure>

<p>这样 <code>foo</code> 就会跟踪 <code>o/main</code> 了。如果当前就在 foo 分支上, 还可以省略 foo：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -u o/main</span><br></pre></td></tr></table></figure>

<h3 id="4、Git-Push的参数"><a href="#4、Git-Push的参数" class="headerlink" title="4、Git Push的参数"></a>4、Git Push的参数</h3><p>在远程跟踪课程中，你已经学到了 Git 是通过当前检出分支的属性来确定远程仓库以及要 push 的目的地的。这是未指定参数时的行为，我们可以为 push 指定参数，语法是：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push &lt;remote&gt; &lt;place&gt;</span><br></pre></td></tr></table></figure>

<p><code>&lt;place&gt;</code> 参数是什么意思呢？我们稍后会深入其中的细节, 先看看例子, 这个命令是:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin main</span><br></pre></td></tr></table></figure>

<p>把这个命令翻译过来就是：</p>
<p><em>切到本地仓库中的“main”分支，获取所有的提交，再到远程仓库“origin”中找到“main”分支，将远程仓库中没有的提交记录都添加上去，搞定之后告诉我。</em></p>
<p>我们通过“place”参数来告诉 Git 提交记录来自于 main, 要推送到远程仓库中的 main。它实际就是要同步的两个仓库的位置。</p>
<p>需要注意的是，因为我们通过指定参数告诉了 Git 所有它需要的信息, 所以它就忽略了我们所检出的分支的属性！</p>
<p>如</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin foo</span><br></pre></td></tr></table></figure>

<p>就是将本地的foo分支同步到远程origin 的foo分支</p>
<h3 id="5、-lt-place-gt-参数详解"><a href="#5、-lt-place-gt-参数详解" class="headerlink" title="5、&lt;place&gt;参数详解"></a>5、&lt;place&gt;参数详解</h3><p>当为 git push 指定 place 参数为 <code>main</code> 时，我们同时指定了提交记录的来源和去向。</p>
<p>你可能想问 —— 如果来源和去向分支的名称不同呢？比如你想把本地的 <code>foo</code> 分支推送到远程仓库中的 <code>bar</code> 分支。</p>
<p><strong>要同时为源和目的地指定 <code>&lt;place&gt;</code> 的话，只需要用冒号 <code>:</code> 将二者连起来就可以了：</strong></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin &lt;source&gt;:&lt;destination&gt;</span><br></pre></td></tr></table></figure>

<p>这个参数实际的值是个 refspec，“refspec” 是一个自造的词，意思是 Git 能识别的位置（比如分支 <code>foo</code> 或者 <code>HEAD~1</code>）</p>
<p>一旦你指定了独立的来源和目的地，就可以组织出言简意赅的远程操作命令了，让我们看看演示！</p>
<p><strong>目前状态：</strong></p>
<img src="/2022/04/18/Learn-Git-Branching/git参数1-165028516951426.png" alt="git参数1" style="zoom:60%;">

<p>使用命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin foo^:main</span><br></pre></td></tr></table></figure>

<p><strong>目标状态：</strong></p>
<img src="/2022/04/18/Learn-Git-Branching/git参数2-165028516951425.png" alt="git参数2" style="zoom:60%;">

<p>记住，<code>source</code> 可以是任何 Git 能识别的位置。</p>
<p>这是个令人困惑的命令，但是它确实是可以运行的 —— Git 将 <code>foo^</code> 解析为一个位置，上传所有未被包含到远程仓库里 <code>main</code> 分支中的提交记录。</p>
<ul>
<li>如果你要推送到的目的分支不存在会怎么样呢？没问题！Git 会在远程仓库中根据你提供的名称帮你创建这个分支！</li>
</ul>
<h3 id="6、Git-fetch的参数"><a href="#6、Git-fetch的参数" class="headerlink" title="6、Git fetch的参数"></a>6、Git fetch的参数</h3><p><code>git fetch</code> 的参数和 <code>git push</code> 极其相似。他们的概念是相同的，只是方向相反罢了（因为现在你是下载，而非上传）</p>
<hr>
<p><strong><code>&lt;place&gt;</code> 参数</strong></p>
<p>如果你像如下命令这样为 git fetch 设置 的话：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin foo</span><br></pre></td></tr></table></figure>

<p>Git 会到远程仓库的 <code>foo</code> 分支上，然后获取所有本地不存在的提交，放到本地的 <code>o/foo</code> 上。</p>
<p>注意：Git 会将新提交放到 <code>o/foo</code> 而不是放到我本地的 foo 分支。因为git fetch不会更新你的本地的非远程分支, 只是下载提交记录（这样, 你就可以对远程分支进行检查或者合并了）。因为你可能在 foo 分支上的工作还未完成，你也不想弄乱它。</p>
<hr>
<p>“如果我们指定 <code>&lt;source&gt;:&lt;destination&gt;</code> 会发生什么呢？”</p>
<p>如果你觉得直接更新本地分支很爽，那你就用冒号分隔的 refspec 吧。不过，你不能在当前检出的分支上干这个事，但是其它分支是可以的。</p>
<p>这里有一点是需要注意的 —— <code>source</code> 现在指的是远程仓库中的位置，而 <code>&lt;destination&gt;</code> 才是要放置提交的本地仓库的位置。它与 git push 刚好相反，这是可以讲的通的，因为我们在往相反的方向传送数据。</p>
<p>理论上虽然行的通，但开发人员很少这么做。这里介绍它主要是为了从概念上说明 <code>fetch</code> 和 <code>push</code> 的相似性，只是方向相反罢了。</p>
<p>另外，如果 <code>git fetch</code> 没有参数，它会下载所有的提交记录到各个远程分支。</p>
<h3 id="7、没有source的source"><a href="#7、没有source的source" class="headerlink" title="7、没有source的source"></a>7、没有source的source</h3><p>Git 有两种关于 <code>&lt;source&gt;</code> 的用法是比较诡异的，即你可以在 git push 或 git fetch 时不指定任何 <code>source</code>，方法就是仅保留冒号和 destination 部分，source 部分留空。</p>
<ul>
<li><code>git push origin :side</code></li>
<li><code>git fetch origin :bugFix</code></li>
</ul>
<p>如果 push 空 到远程仓库会如何呢？它会删除远程仓库中的分支！</p>
<p>如：</p>
<p>原状态：</p>
<img src="/2022/04/18/Learn-Git-Branching/nosource-165028516951427.png" alt="nosource" style="zoom:60%;">

<p>执行指令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin:foo</span><br></pre></td></tr></table></figure>

<p>所得结果：</p>
<img src="/2022/04/18/Learn-Git-Branching/nosource2-165028516951428.png" alt="nosource2" style="zoom:60%;">

<p>就是这样子, 我们通过给 push 传空值 source，成功删除了远程仓库中的 <code>foo</code> 分支。</p>
<hr>
<p>如果 fetch 空 到本地，会在本地创建一个新分支。</p>
<p>如：</p>
<img src="/2022/04/18/Learn-Git-Branching/nosource3-165028516951429.png" alt="nosource3" style="zoom:60%;">

<p>执行命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin:bar</span><br></pre></td></tr></table></figure>

<p>所得结果：</p>
<img src="/2022/04/18/Learn-Git-Branching/nosource4-165028516951430.png" alt="nosource4" style="zoom:60%;">

<h3 id="8、git-pull的参数"><a href="#8、git-pull的参数" class="headerlink" title="8、git pull的参数"></a>8、git pull的参数</h3><p><strong>git pull 到头来就是 fetch 后跟 merge 的缩写。你可以理解为用同样的参数执行 git fetch，然后再 merge 你所抓取到的提交记录。</strong></p>
<p>还可以和其它更复杂的参数一起使用, 来看一些例子:</p>
<p>以下命令在 Git 中是等效的:</p>
<p><code>git pull origin foo</code> 相当于：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin foo; git merge o/foo</span><br></pre></td></tr></table></figure>

<p>还有…</p>
<p><code>git pull origin bar~1:bugFix</code> 相当于：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin bar~1:bugFix; git merge bugFix</span><br></pre></td></tr></table></figure>

<p>看到了? git pull 实际上就是 fetch + merge 的缩写, git pull 唯一关注的是提交最终合并到哪里（也就是为 git fetch 所提供的 destination 参数）</p>
<p>如果我们指定要抓取的 place，所有的事情都会跟之前一样发生，只是增加了 merge 操作。</p>
<hr>
<p>pull 也可以用 source:destination </p>
<p>例如：</p>
<p>原状态：</p>
<img src="/2022/04/18/Learn-Git-Branching/pull的参数-165028516951431.png" alt="pull的参数" style="zoom:60%;">

<p>使用命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull origin main:foo</span><br></pre></td></tr></table></figure>

<p>所得结果：</p>
<img src="/2022/04/18/Learn-Git-Branching/pull参数2-165028516951432.png" alt="pull参数2" style="zoom:60%;">

<p>它先在本地创建了一个叫 <code>foo</code> 的分支，从远程仓库中的 main 分支中下载提交记录，并合并到 <code>foo</code>，然后再 merge 到我们的当前检出的分支 <code>bar</code> 上。</p>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2022/04/18/C-%E4%B8%AD%E7%9A%84operator-new%E5%92%8Cplacement-new/">C++中的operator_new和placement_new</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2022-04-18</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</a><i class="fa fa-angle-right" aria-hidden="true"></i><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/C/">C++</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/C/">C++</a></span><div class="content"><h1 id="C-中的operator-new和placement-new"><a href="#C-中的operator-new和placement-new" class="headerlink" title="C++中的operator new和placement new"></a>C++中的operator new和placement new</h1><h2 id="1-new的三种面貌"><a href="#1-new的三种面貌" class="headerlink" title="1.new的三种面貌"></a>1.new的三种面貌</h2><p>C++中使用new运算符产生一个存在于Heap（堆）上对象时，实际上调用了operator new()函数和placement new()函数。在使用new创建堆对象时，我们要清楚认清楚new的三种面貌，分别是：new operator、operator new()和placement new()。placement new()也称为定位构造对象。</p>
<h3 id="1-1new-operator"><a href="#1-1new-operator" class="headerlink" title="1.1new operator"></a>1.1new operator</h3><p>new operator是C++保留的关键字，我们无法改变其含义，但我们可以改变new完成它功能时调用的两个函数，<strong>operator new()和placement new()<strong>。</strong>也就是说我们在使用运算符new时，其最终是通过调用operator new()和placement new()来完成堆对象的创建工作</strong>。使用new operator时，其完成的工作有如下三步： </p>
<p><img src="/2022/04/18/C-%E4%B8%AD%E7%9A%84operator-new%E5%92%8Cplacement-new/1.png" alt="img"></p>
<p>因此，当我们经常按照如下方式使用new operator时：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">string* sp=<span class="keyword">new</span> <span class="built_in">string</span>(“hello world”);</span><br></pre></td></tr></table></figure>

<p>实际上等价于：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//第一步：申请原始空间，行为类似于malloc</span></span><br><span class="line"><span class="keyword">void</span>* raw=<span class="keyword">operator</span> <span class="built_in"><span class="keyword">new</span></span>(<span class="built_in">strlen</span>(“hello world”));</span><br><span class="line"><span class="comment">//第二步：通过placement new调用string类的构造函数，初始化申请空间</span></span><br><span class="line"><span class="keyword">new</span> (raw) <span class="built_in">string</span>(“hello world”);</span><br><span class="line"><span class="comment">//第三部：返回对象指针</span></span><br><span class="line">string* sp=<span class="keyword">static_cast</span>&lt;string*&gt;(raw);</span><br></pre></td></tr></table></figure>

<h3 id="1-2-operator-new"><a href="#1-2-operator-new" class="headerlink" title="1.2 operator new()"></a>1.2 operator new()</h3><p><strong>operator new()用于申请Heap空间，功能类似于C语言的库函数malloc()，尝试从堆上获取一段内存空间，如果成功则直接返回，如果失败则转而去调用一个new handler，然后抛出一个bad_alloc异常。</strong>operator new()的函数原型一般为</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="keyword">operator</span> <span class="title">new</span> <span class="params">(std::<span class="keyword">size_t</span> size)</span> <span class="title">throw</span> <span class="params">(std::bad_alloc)</span></span>;</span><br></pre></td></tr></table></figure>

<p>具体实现如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> *__CRTDECL <span class="keyword">operator</span> <span class="title">new</span><span class="params">(<span class="keyword">size_t</span> size)</span> <span class="title">throw</span> <span class="params">(std::bad_alloc)</span></span></span><br><span class="line"><span class="function"></span>&#123;       </span><br><span class="line">    <span class="comment">// try to allocate size bytes</span></span><br><span class="line">    <span class="keyword">void</span> *p;</span><br><span class="line">    <span class="keyword">while</span> ((p = <span class="built_in">malloc</span>(size)) == <span class="number">0</span>)      <span class="comment">//申请空间</span></span><br><span class="line">        <span class="keyword">if</span> (_callnewh(size) == <span class="number">0</span>)        <span class="comment">//若申请失败则调用处理函数</span></span><br><span class="line">        &#123;       </span><br><span class="line">            <span class="comment">// report no memory</span></span><br><span class="line">            <span class="keyword">static</span> <span class="keyword">const</span> std::bad_alloc nomem;</span><br><span class="line">            _RAISE(nomem);               <span class="comment">//#define _RAISE(x) ::std:: _Throw(x) 抛出nomem的异常</span></span><br><span class="line">        &#125;</span><br><span class="line">    <span class="keyword">return</span> (p);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>注意：</strong>  </p>
<p>（1）函数后添加throw表示可能会抛出throw后括号内的异常；  </p>
<p>（2）operator new()分为全局和类成员。当为类成员函数时，使用new产生类对象时调用的则是其成员函数operator new()。如果要重载全局的operator new会改变所有默认的operator new的方式，所以必须要注意。正如new与delete相互对应，operator new与operator delete也是一一对应，<strong>如果重载了operator new，那么理应重载operator delete。</strong></p>
<h3 id="1-3-placement-new-："><a href="#1-3-placement-new-：" class="headerlink" title="1.3 placement new()："></a>1.3 <strong>placement new()：</strong></h3><p> 一般来说，使用new申请空间时，是从系统的堆中分配空间，申请所得空间的位置根据当时内存实际使用情况决定。但是，在某些特殊情况下，可能需要在程序员指定的特定内存创建对象，这就是所谓的“定位放置new”（placement new）操作。<strong>placement new()是一个特殊的operator new()<strong>，因为其是operator new()函数的重载版本，只是取了个别名叫作placement new罢了。</strong>作用是在已经获得的堆空间上调用类构造函数来初始化对象，也就是定位构造对象。</strong>通常情况下，构造函数是由编译器自动调用的，但是不排除程序员手动调用的可能性，比如对一块未初始化的内存进行处理，获得想要的对象，这是需要求助于placement new()。<strong>placement new()是C++标准库的一部分，被申明在头文件<code>&lt;new&gt;</code>中</strong>，其函数原型是：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="keyword">operator</span> <span class="title">new</span><span class="params">(std::<span class="keyword">size_t</span>, <span class="keyword">void</span>* __p)</span></span>;</span><br></pre></td></tr></table></figure>

<p>具体实现如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="keyword">operator</span> <span class="title">new</span><span class="params">(std::<span class="keyword">size_t</span>, <span class="keyword">void</span>* __p)</span> <span class="title">throw</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> __p;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>注意：</strong>  </p>
<p>（1）placement new()的函数原型不是</p>
<p><code>void* placement new(std::size_t, void* __p)</code>;  </p>
<p>（2）placement new只是operator new()的一个重载，多了一个已经申请好的空间，由void* __p指定；  </p>
<p>（3）用法是<code>new (addr) constructor()</code>，对addr指定的内存空间调用构造函数进行初始化。<strong>为何称为placement new，从其用法可以看出只是用于调用构造函数。</strong></p>
<p>定位放置new操作的语法形式不同于普通的new操作。例如，一般都用如下语句A* p=new A;申请空间，而定位放置new操作则使用如下语句A* p=new (ptr) A;申请空间，其中ptr就是程序员指定的内存首地址。考察如下程序。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span>&#123;</span></span><br><span class="line">    <span class="keyword">int</span> num;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">A</span>()&#123;</span><br><span class="line">        cout&lt;&lt;<span class="string">&quot;A&#x27;s constructor&quot;</span>&lt;&lt;endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">A</span>()&#123;</span><br><span class="line">        cout&lt;&lt;<span class="string">&quot;~A&quot;</span>&lt;&lt;endl;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">show</span><span class="params">()</span></span>&#123;</span><br><span class="line">        cout&lt;&lt;<span class="string">&quot;num:&quot;</span>&lt;&lt;num&lt;&lt;endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> mem[<span class="number">100</span>];</span><br><span class="line">    mem[<span class="number">0</span>]=<span class="string">&#x27;A&#x27;</span>;</span><br><span class="line">    mem[<span class="number">1</span>]=<span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    mem[<span class="number">2</span>]=<span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    mem[<span class="number">3</span>]=<span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    cout&lt;&lt;(<span class="keyword">void</span>*)mem&lt;&lt;endl;</span><br><span class="line">    A* p=<span class="built_in"><span class="keyword">new</span></span> (mem) A;</span><br><span class="line">    cout&lt;&lt;p&lt;&lt;endl;</span><br><span class="line">    p-&gt;<span class="built_in">show</span>();</span><br><span class="line">    p-&gt;~<span class="built_in">A</span>();</span><br><span class="line">    <span class="built_in">getchar</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>程序运行结果：  </p>
<p>0024F924  </p>
<p>A’s constructor </p>
<p>0024F924  </p>
<p>num:65  </p>
<p>~A</p>
<p>阅读以上程序，注意以下几点。  </p>
<p>（1）<strong>用定位放置new操作，既可以在栈(stack)上生成对象，也可以在堆（heap）上生成对象。</strong>如本例就是在栈上生成一个对象。</p>
<p>（2）<em><em>使用语句A</em> p=new (mem) A;定位生成对象时，指针p和数组名mem指向同一片存储区。所以，与其说定位放置new操作是申请空间，还不如说是利用已经请好的空间，真正的申请空间的工作是在此之前完成的。</em>*</p>
<p>（3）*<em>使用语句A <em>p=new (mem) A;定位生成对象时，会自动调用类A的构造函数，但是由于对象的空间不会自动释放（对象实际上是借用别人的空间），所以必须显示的调用类的析构函数，如本例中的p-&gt;~A()。</em></em></p>
<p>（4）<strong>万不得已才使用placement new，只有当你真的在意对象在内存中的特定位置时才使用它。</strong>例如，你的硬件有一个内存映像的I/O记时器设备，并且你想放置一个Clock对象在那个位置。</p>
<p><strong>总结：</strong>  </p>
<p>（1）<strong>若想在堆上建立一个对象，应该用new操作符。</strong>它既分配内存又调用其构造函数进行初始化。 </p>
<p>（2）<strong>若仅仅想分配内存，应该调用operator new()（当然malloc也是可以）,他不会调用构造函数</strong>。若想定制自己在堆对象被建立时的内存分配过程，应该重写自己的operator new()。  </p>
<p>（3）<strong>若想在一块已经获得的内存空间上建立一个对象，应该用placement new。</strong>虽然在实际开发过程中，很少需要重写operator new()，使用内置的operator new()即可完成大部分程序所需的功能。但知道这些，有助于一个C++程序猿对C++内存的管理有个清楚的认识。</p>
<hr>
<h2 id="2-了解delete和operator-delete"><a href="#2-了解delete和operator-delete" class="headerlink" title="2.了解delete和operator delete()"></a>2.了解delete和operator delete()</h2><p><strong>为了避免内存泄漏，每个动态内存分配必须与一个等同相反的 deallocation 对应。</strong></p>
<p>operator delete与delete操作符的关系与operator new与new操作符是一样的。<strong>delete用于使用使用new申请的空间，operator delete用于释放operator new申请的空间（类似于malloc与free）。</strong></p>
<p><strong>那谁来清理placement new初始化的内存内容呢？唯一办法就是调用对象的析构函数。</strong></p>
<p>示例代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">string* sp=<span class="keyword">new</span> <span class="built_in">string</span>(“hello world”);</span><br><span class="line"><span class="keyword">delete</span> sp;</span><br></pre></td></tr></table></figure>

<p>第一行代码在上文已经剖析，那么当调用delete sp时，发生了什么?</p>
<p>delete sp等价于：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ps-&gt;~<span class="built_in">string</span>(); <span class="comment">//用于清理内存内容，对应placement new</span></span><br><span class="line"><span class="function"><span class="keyword">operator</span> <span class="title">delete</span><span class="params">(ps)</span></span>;<span class="comment">//释放内存空间，对应于operator new()</span></span><br></pre></td></tr></table></figure>

<p>其中operator delete()的函数原型为：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="keyword">void</span> *memoryToBeDeallocated)</span></span>;</span><br></pre></td></tr></table></figure>



<hr>
<p><strong>总的来看，可以理解为，operator new()是用来申请足够的内存空间，而placement new()则是用来在申请好的内存空间中调用构造函数对内存空间进行初始化，是利用已经申请好的空间，并且这个空间可以是在栈上的。operator new申请的内存空间使用operator delete来释放，placement new借用的空间调用对象的析构函数来归还。</strong></p>
<p>参考文献：</p>
<p> [1]  Scott Meyers.More Effective C++（第三版）[M].北京：电子工业出版社，2011.1. </p>
<p> [2] <a href="**https://cloud.tencent.com/developer/article/1177460#:~:text=placement%20new%20()%E6%98%AFC++%E6%A0%87%E5%87%86%E5%BA%93%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86%EF%BC%8C%E8%A2%AB%E7%94%B3%E6%98%8E%E5%9C%A8%E5%A4%B4%E6%96%87%E4%BB%B6%20%3Cnew%3E%20%E4%B8%AD%EF%BC%8C%E5%85%B6%E5%87%BD%E6%95%B0%E5%8E%9F%E5%9E%8B%E6%98%AF%EF%BC%9A,void*%20operator%20new(std::size_t,%20void*%20__p);**">C++ new的三种面貌</a></p>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2021/12/07/C-%E4%B8%AD%E7%9A%84new-delete%E5%92%8Cmalloc-free/">C++中的new/delete和malloc/free</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2021-12-07</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</a><i class="fa fa-angle-right" aria-hidden="true"></i><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/C/">C++</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/C/">C++</a></span><div class="content"><hr>
<h1 id="C-中new-delete和malloc-free的区别"><a href="#C-中new-delete和malloc-free的区别" class="headerlink" title="C++ 中new/delete和malloc/free的区别"></a>C++ 中new/delete和malloc/free的区别</h1><p>参考链接:<a target="_blank" rel="noopener" href="https://www.cnblogs.com/iloverain/p/5633281.html">https://www.cnblogs.com/iloverain/p/5633281.html</a></p>
<hr>
<h2 id="C语言中的函数malloc和free"><a href="#C语言中的函数malloc和free" class="headerlink" title="C语言中的函数malloc和free"></a>C语言中的函数malloc和free</h2><p>(1) 函数malloc和free在**头文件&lt;stdlib.h&gt;**中的原型及参数</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> * <span class="title">malloc</span><span class="params">(<span class="keyword">size_t</span> size)</span></span></span><br></pre></td></tr></table></figure>

<p><strong>动态配置内存，大小由size决定，返回值成功时为任意类型指针，失败时为NULL。</strong> </p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">free</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span></span><br></pre></td></tr></table></figure>

<p><strong>释放动态申请的内存空间，调用free( )后ptr所指向的内存空间被收回，如果ptr指向未知地方或者指向的空间已被收回，则会发生不可预知的错误，如果ptr为NULL，free不会有任何作用。</strong></p>
<hr>
<p>(2) C语言中典型用法</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">T *p = ( T* )<span class="built_in">malloc</span>( <span class="keyword">sizeof</span>(T) * n)  <span class="comment">//T为任意数据类型</span></span><br><span class="line"><span class="keyword">if</span>(<span class="literal">NULL</span>==p)&#123;</span><br><span class="line">	<span class="built_in">printf</span>(“<span class="built_in">malloc</span> fail!\n”);</span><br><span class="line">	……<span class="comment">//相关资源收回的处理</span></span><br><span class="line">	<span class="built_in">exit</span>(<span class="number">-1</span>);</span><br><span class="line">&#125;</span><br><span class="line">… …<span class="comment">//此过程不能改变指针p的指向</span></span><br><span class="line"><span class="built_in">free</span>(p);</span><br></pre></td></tr></table></figure>

<p>注意：malloc后通常要对返回值进行判断，避免发生不必要的错误。</p>
<hr>
<p>(3) 内存说明</p>
<p>malloc函数动态申请的内存空间是<strong>在堆里</strong>(而一般局部变量存于栈里)，并且<strong>该段内存不会被初始化</strong>，与全局变量不一样，如果不采用手动free()加以释放，则该段内存一直存在，直到程序退出才被系统，所以为了合理使用内存，在不适用该段内存时，应该调用free()。另外，<strong>如果在一个函数里面使用过malloc，最好要配对使用free，否则容易造成内存泄露。</strong></p>
<hr>
<h2 id="C-中的运算符new和delete"><a href="#C-中的运算符new和delete" class="headerlink" title="C++中的运算符new和delete"></a>C++中的运算符new和delete</h2><p><strong>new和delete是C++中的运算符</strong>，不是库函数，不需要库的支持，同时，他们是封装好的重载运算符，并且可以再次进行重载。</p>
<p>C++中使用new的例子：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//new运算的例子</span></span><br><span class="line"><span class="keyword">new</span> <span class="keyword">int</span>  <span class="comment">//开辟一个存放整数的存储空间，返回一个指向该存储空间的地址</span></span><br><span class="line"><span class="keyword">new</span> <span class="built_in"><span class="keyword">int</span></span>(<span class="number">100</span>)  <span class="comment">//同上，并指定该整数的初值为100</span></span><br><span class="line"><span class="keyword">new</span> <span class="keyword">char</span>[<span class="number">100</span>] <span class="comment">//开辟一个存放字符数组（100个元素）的空间，返回首地址</span></span><br><span class="line"><span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">4</span>][<span class="number">5</span>]<span class="comment">//开辟一个存放二维数组的空间，返回首元素的地址</span></span><br><span class="line"><span class="keyword">float</span> *p=<span class="keyword">new</span> <span class="built_in"><span class="keyword">float</span></span>(<span class="number">3.14157</span>) <span class="comment">//开辟一个存放单精度的空间，并指定该数的初值为3.14157，</span></span><br><span class="line">                              将返回的该空间的地址赋给指针变量p</span><br></pre></td></tr></table></figure>

<p><strong>注意：用new分配数组空间不能指定初值，若无法正常分配，则new会返回一个空指针NULL,用户可以根据此判断是否分配成功。</strong></p>
<p>delete运算符使用的一般格式：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">delete  指针变量  </span><br><span class="line">delete  [ ] 指针变量（对数组）</span><br></pre></td></tr></table></figure>

<p>（1）     <strong>new是动态分配内存的运算符，自动计算需要分配的空间</strong>，在C++中，它属于重载运算符，可以对多种数据类型形式进行分配内存空间，比如int型、char型、结构体型和类等的动态申请的内存分配，分配类的内存空间时，同时调用类的构造函数，<strong>对内存空间进行初始化，即完成类的初始化工作。</strong></p>
<p>（2）     <strong>delete是撤销动态申请的内存运算符。</strong>delete与new通常配对使用，与new的功能相反，可以对多种数据类型形式的内存进行撤销，包括类，撤销类的内存空间时，<strong>它要调用其析构函数，完成相应的清理工作，收回相应的内存资源。</strong></p>
<p>（3）     典型用法</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//类的类型 *p = new 类的类型； delete p；如：</span></span><br><span class="line"><span class="keyword">int</span> *p = <span class="keyword">new</span> <span class="keyword">int</span>;                   </span><br><span class="line"><span class="keyword">delete</span> p;</span><br><span class="line"><span class="keyword">char</span> *p = <span class="keyword">new</span> <span class="keyword">char</span>;</span><br><span class="line"><span class="keyword">delete</span> p;</span><br><span class="line"><span class="comment">//注意，指针p存于栈中，p所指向的内存空间却是在堆中。</span></span><br><span class="line">Obj * p = <span class="keyword">new</span> Obj[<span class="number">100</span>];           </span><br><span class="line"><span class="keyword">delete</span> []p;</span><br><span class="line"><span class="comment">//注意，new申请数组，delete删除的形式需要加括号“[ ]”，表示对数组空间的操作，总之，申请形式如何，释放的形式就如何。</span></span><br></pre></td></tr></table></figure>

<p>（4）     内存说明。<strong>new申请的内存也是存于堆中，所以在不需要使用时，需要delete手动收回。</strong></p>
<hr>
<h2 id="new-delete与malloc-free之间的联系和区别"><a href="#new-delete与malloc-free之间的联系和区别" class="headerlink" title="new/delete与malloc/free之间的联系和区别"></a>new/delete与malloc/free之间的联系和区别</h2><ul>
<li><p>(1)     <strong>malloc/free和new/delete的联系</strong></p>
<ul>
<li>a）     存储方式相同。malloc和new动态申请的内存都位于堆中。申请的内存都不能自动被操作系统收回，都需要配套的free和delete来释放。</li>
<li>b）     除了带有构造函数和析构函数的类等数据类型以外，对于一般数据类型，如int、char等等，两组动态申请的方式可以通用，作用效果一样，只是形式不一样。</li>
<li>c）     内存泄漏对于malloc或者new都可以检查出来的，区别在于new可以指明是那个文件的那一行，而malloc没有这些信息。</li>
<li>d）     两组都需要配对使用，malloc配free，new配delete，注意，这不仅仅是习惯问题，如果不配对使用，容易造成内存泄露。同时，在C++中，两组之间不能混着用，虽说有时能编译过，但容易存在较大的隐患。</li>
</ul>
</li>
<li><p><strong>(2)     malloc/free和new/delete的区别</strong></p>
<ul>
<li><p>a）     malloc和free返回void类型指针，new和delete直接带具体类型的指针。</p>
</li>
<li><p>b）     malloc和free属于C语言中的函数，需要库的支持，而new/delete是C++中的运算符，况且可以重载，所以new/delete的执行效率高些。C++中为了兼用C语法，所以保留malloc和free的使用，但建议尽量使用new和delete。</p>
</li>
<li><p>c）     在C++中， new是类型安全的，而malloc不是。例如：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span>* p = <span class="keyword">new</span> <span class="keyword">char</span>[<span class="number">10</span>];          <span class="comment">// 编译时指出错误</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">delete</span> []p;                   <span class="comment">//对数组需要加中括号“[ ]”</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span>* p = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">char</span> )*<span class="number">10</span>);  <span class="comment">// 编译时无法指出错误</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">free</span> (p);                    <span class="comment">//只需要所释放内存的头指针</span></span><br></pre></td></tr></table></figure></li>
<li><p>d）     使用new动态申请类对象的内存空间时，类对象的构建要调用构造函数，相当于对内存空间进行了初始化。而malloc动态申请的类对象的内存空间时，不会初始化，也就是说申请的内存空间无法使用，因为类的初始化是由构造函数完成的。delete和free的意义分别于new和malloc相反。</p>
</li>
<li><p>e）     不能用malloc和free来完成类对象的动态创建和删除。</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="补充：-C-C-程序的内存分配介绍"><a href="#补充：-C-C-程序的内存分配介绍" class="headerlink" title="补充： C/C++程序的内存分配介绍"></a>补充： C/C++程序的内存分配介绍</h3><p>（1）栈内存分配运算内置于处理器的指令集中，一般使用寄存器来存取，效率很高，但是分配的内存容量有限。 一般局部变量和函数参数的暂时存放位置。</p>
<p>（2） 堆内存，亦称动态内存。如malloc和new申请的内存空间。动态内存的生存期由程序员自己决定，使用非常灵活。 </p>
<p>（3）全局代码区：从静态存储区域分配。内存在程序编译的时候就已经分配好，这块内存在程序的整个运行期间都存在。例如全局变量，static变量。 </p>
<p>（4）常量区：文字常量分配在文字常量区，程序结束后由系统释放。 </p>
<p>（5）代码区：存放整个程序的代码，因为存储是数据和代码分开存储的。</p>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2021/12/02/%E4%BD%8D%E5%9B%BE%E6%8E%92%E5%BA%8F/">位图排序</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2021-12-02</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%AE%97%E6%B3%95/">算法</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/%E7%AE%97%E6%B3%95%E8%89%BA%E6%9C%AF/">算法艺术</a></span><div class="content"><h1 id="位图排序算法"><a href="#位图排序算法" class="headerlink" title="位图排序算法"></a>位图排序算法</h1><p><strong>位图法：bitmap，就是用每一位来存放某种状态，适用于大规模数据，但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的。</strong></p>
<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>​        位图排序是一种<strong>效率极高(复杂度可达O(n))并且很节省空间</strong>的一种排序方法，但是这种排序方法对输入的数据是有比较严格的要求(<strong>数据不能重复，大致知道数据的范围</strong>)。位图排序即利用位图或者位向量来表示集合。可以说算法中用到位操作的很多，因为速度快，空间小，比如还有<strong>bitmask dp</strong>（例题：<a target="_blank" rel="noopener" href="https://leetcode-cn.com/problems/shortest-path-visiting-all-nodes/">847. 访问所有节点的最短路径</a>。）</p>
<h3 id="举个例子"><a href="#举个例子" class="headerlink" title="举个例子"></a>举个例子</h3><p>对unsigned没有重复的数字进行排序，假设我们要对0-7内的5个元素(4,7,2,5,3)排序。那么我们就可以采用Bit-map的方法来达到排序的目的。要表示8个数，我们就只需要8个Bit（1Bytes），首先我们开辟1Byte的空间，将这些空间的所有Bit位都置为0，如下图：</p>
<p><img src="/2021/12/02/%E4%BD%8D%E5%9B%BE%E6%8E%92%E5%BA%8F/8394323_1303655564K05X.jpg" alt="8394323_1303655564K05X"></p>
<p>然后遍历这5个元素，首先第一个元素是4，那么就把4对应的位置为1（可以这样操作：p+(i/8)|(0x01&lt;&lt;(i%8)）当然了这里的操作涉及到Big-ending和Little-ending的情况，这里默认为Big-ending）,因为是从零开始的，所以要把第五位置为一（如下图）：</p>
<p><img src="/2021/12/02/%E4%BD%8D%E5%9B%BE%E6%8E%92%E5%BA%8F/8394323_1303655564rRn3.jpg" alt="8394323_1303655564rRn3"></p>
<p>接着再处理第二个元素7，将第八位置为1,，接着再处理第三个元素，一直到最后处理完所有的元素，将相应的位置为1，这时候的内存的Bit位的状态如下：</p>
<p><img src="/2021/12/02/%E4%BD%8D%E5%9B%BE%E6%8E%92%E5%BA%8F/8394323_1303655564lhFb.jpg" alt="8394323_1303655564lhFb"></p>
<p>最后我们现在遍历一遍Bit区域，将该位是一的位的编号输出（2，3，4，5，7），这样就达到了排序的目的。</p>
<p><strong>这样只需要开辟8个Bit（1Bytes）就可以记录每个数据并达到排序的目的。</strong></p>
<h2 id="算法实现（C-）"><a href="#算法实现（C-）" class="headerlink" title="算法实现（C++）"></a>算法实现（C++）</h2><p>示例中，位图法对不重复unsigned int 数的排序。</p>
<p>产生不重复unsigned int 数据请参考：<a target="_blank" rel="noopener" href="http://blog.csdn.net/lanzhihui_10086/article/details/50096265">产生数据</a></p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;fstream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;bitset&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;ctime&gt;</span></span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">const</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> max_num=<span class="number">20000</span>;<span class="comment">//数字最大</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> max_count=<span class="number">10000</span>;<span class="comment">//数据个数</span></span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	bitset&lt;max_num+1&gt; bit;</span><br><span class="line">	<span class="keyword">int</span> tmp=<span class="number">0</span>;</span><br><span class="line"> </span><br><span class="line">	<span class="comment">//以下读取磁盘文件中的数据</span></span><br><span class="line">	ifstream in_ran;</span><br><span class="line">	in_ran.<span class="built_in">open</span>(<span class="string">&quot;F:\\gz\\random.txt&quot;</span>,ios::in|ios::_Nocreate);</span><br><span class="line"> </span><br><span class="line">	<span class="keyword">if</span>(in_ran.<span class="built_in">fail</span>())</span><br><span class="line">	&#123;</span><br><span class="line">		cout&lt;&lt;<span class="string">&quot;打开文件失败!&quot;</span>&lt;&lt;endl;</span><br><span class="line">		<span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">	&#125;</span><br><span class="line"> </span><br><span class="line">	<span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;max_count;i++)</span><br><span class="line">	&#123;</span><br><span class="line">		in_ran&gt;&gt;tmp;    <span class="comment">//从文件中读取数据</span></span><br><span class="line">		bit.<span class="built_in">set</span>(tmp,<span class="number">1</span>);<span class="comment">//从磁盘文件读取100个整数，顺序存放在a数组中</span></span><br><span class="line">	&#125;</span><br><span class="line">	in_ran.<span class="built_in">close</span>();</span><br><span class="line"> </span><br><span class="line">	<span class="comment">//以下向磁盘文件写入数据--排序后的数据</span></span><br><span class="line">	ofstream out_sort;</span><br><span class="line">	out_sort.<span class="built_in">open</span>(<span class="string">&quot;F:\\gz\\random_sort.txt&quot;</span>,ios::out);</span><br><span class="line"> </span><br><span class="line">	<span class="keyword">if</span>(out_sort.<span class="built_in">fail</span>())</span><br><span class="line">	&#123;</span><br><span class="line">		cout&lt;&lt;<span class="string">&quot;打开文件失败!&quot;</span>&lt;&lt;endl;</span><br><span class="line">		<span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">	&#125;</span><br><span class="line"> </span><br><span class="line">	<span class="keyword">for</span>(<span class="keyword">int</span> j=<span class="number">0</span>;j&lt;max_num;j++)<span class="comment">//输出排序后的随机数</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="keyword">if</span>(bit[j]==<span class="number">1</span>)</span><br><span class="line">		&#123;</span><br><span class="line">			out_sort&lt;&lt;j&lt;&lt;<span class="string">&quot; &quot;</span>;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	out_sort.<span class="built_in">close</span>();</span><br><span class="line"> </span><br><span class="line">	<span class="built_in">system</span>(<span class="string">&quot;pause&quot;</span>);</span><br><span class="line">	<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>与以往先将读取的数据储存在数组中，然后在进行排序相比，此排序方法没有中间数组作为传递，而且只须查询遍历一次数据即可得出排序后的结果。特别适用于无重复的整数排序。</p>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2021/11/10/%E7%90%86%E8%A7%A3C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8/">理解C++虚函数表</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2021-11-10</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</a><i class="fa fa-angle-right" aria-hidden="true"></i><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/C/">C++</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/C/">C++</a></span><div class="content"><p>参考：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/64f3b9c22898">https://www.jianshu.com/p/64f3b9c22898</a></p>
<h1 id="理解-C-虚函数表"><a href="#理解-C-虚函数表" class="headerlink" title="理解 C++ 虚函数表"></a>理解 C++ 虚函数表</h1><h1 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h1><p>虚表是 C++ 中一个十分重要的概念，面向对象编程的多态性在 C++ 中的实现全靠虚表来实现。在聊虚表之前我们先回顾一下什么是多态性。</p>
<p>多态实际上就是让一个父类指针，通过赋予子类对象的地址，可以呈现出多种形态和功能。如果这么说比较抽象的话，我们看一个例子就明白了：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Base</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> m_tag;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Base</span>(<span class="keyword">int</span> tag) : <span class="built_in">m_tag</span>(tag) &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">print</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Base::print() called&quot;</span> &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">vPrint</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Base::vPrint() called&quot;</span> &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">printTag</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Base::m_tag of this instance is: &quot;</span> &lt;&lt; m_tag &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Derived</span> :</span> <span class="keyword">public</span> Base &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Derived</span>(<span class="keyword">int</span> tag) : <span class="built_in">Base</span>(tag) &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">print</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Derived1::print() called&quot;</span> &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">vPrint</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;Derived::vPrint() called&quot;</span> &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>在上面的代码中，我们声明了一个父类 <code>Base</code>，和它的一个派生类 <code>Derived</code>，其中 <code>print()</code> 实例方法是非虚函数，而其余两个实例方法被声明为了虚函数。并且在子类中我们重新了 <code>print()</code> 和 <code>vPrint()</code>。下面我们构造出一个 <code>Derived</code> 实例，并分别将其地址赋给 <code>Base</code> 指针和 <code>Derived</code> 指针：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span> </span>&#123;</span><br><span class="line">    Derived *foo = <span class="keyword">new</span> <span class="built_in">Derived</span>(<span class="number">1</span>);</span><br><span class="line">    Base *bar = foo;</span><br><span class="line">    </span><br><span class="line">    foo-&gt;<span class="built_in">print</span>();</span><br><span class="line">    foo-&gt;<span class="built_in">vPrint</span>();</span><br><span class="line">    </span><br><span class="line">    bar-&gt;<span class="built_in">print</span>();</span><br><span class="line">    bar-&gt;<span class="built_in">vPrint</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们看看程序运行的结果：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Derived1::<span class="built_in">print</span>() <span class="function">called</span></span><br><span class="line"><span class="function"><span class="title">Derived::vPrint</span><span class="params">()</span> called</span></span><br><span class="line"><span class="function"><span class="title">Base::print</span><span class="params">()</span> called</span></span><br><span class="line"><span class="function"><span class="title">Derived::vPrint</span><span class="params">()</span> called</span></span><br></pre></td></tr></table></figure>

<p>可以看到，对于 <code>Derived</code> 指针的操作正如它应该表现的样子，然而当我们把相同对象的地址赋给 <code>Base</code> 指针时，可以发现它的非虚函数竟然表现出了父类的行为，并没有被重写的样子。</p>
<p>这是什么原因呢？</p>
<h1 id="C-类的实质是什么"><a href="#C-类的实质是什么" class="headerlink" title="C++ 类的实质是什么"></a>C++ 类的实质是什么</h1><p>首先我们要明白 C++ 中类的实质到底是什么。实际上，类在 C++ 中就是 <strong>struct</strong> （结构体）的一种扩展，允许了更高级的继承和虚函数。那么也就是说，结构体缺少的实际上就是虚函数。</p>
<p>对于一般的成员变量，它和结构体在内存布局上是完全一样的，不管是顺序还是内存对齐，完全一致。而一个类的方法地址并不会存储在一个实例的内存中。对于非虚函数，它们在内存中的地址是唯一的，你可以把它想象成普通函数，只不过第一个参数是 <code>this</code> 指针，在通过类对象指针调用时，编译器会根据类型找到相应非虚函数的地址，这个工作是编译时完成的。</p>
<p><img src="/2021/11/10/%E7%90%86%E8%A7%A3C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8/1507403-fdadc14d4e99d1d2.png" alt="1.1"></p>
<p>也就是说，什么指针指向什么函数这是固定的，反正指针如果是 <code>Base *</code>，那我就直接执行 <code>Base::print()</code> 函数。</p>
<h1 id="揭开-vTable-的神秘面纱"><a href="#揭开-vTable-的神秘面纱" class="headerlink" title="揭开 vTable 的神秘面纱"></a>揭开 vTable 的神秘面纱</h1><p>既然非虚函数实现这么简单，那虚函数是不是会很复杂？其实并不是那么复杂。虚函数的地址被存储一张叫做虚表的东西里，我们其实很容易拿到这个虚表。下面我们通过 dump memory 的方式来揪出一个类的虚表：</p>
<p><img src="/2021/11/10/%E7%90%86%E8%A7%A3C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8/1.2.png" alt="1.2"></p>
<p>看到我选中的那个字节，那是我们的一个实例变量，在这个实例变量的前面有 8 个字节的内容，那实际就是虚表的地址了，我们尝试将这个地址所指向的内容拿出来：</p>
<p><img src="/2021/11/10/%E7%90%86%E8%A7%A3C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8/1.3.png" alt="1.3"></p>
<p>这就是虚表的内容了，什么？你不信，下面我就把虚表中第一个函数揪出来执行一下：</p>
<p><img src="/2021/11/10/%E7%90%86%E8%A7%A3C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8/1.4.png" alt="1.4"></p>
<p>可以看到，<code>Derived</code> 类中重写的 <code>vPrint()</code> 方法已经被执行。这就说明虚函数在执行时是一个动态的过程，并不是在编译时就确定下来要执行哪一个函数，而是运行时从虚表查到真正要执行的函数的地址，然后再将 <code>this</code> 指针传入执行。</p>
<p>到这里，我们已经大致了解了虚函数是怎样工作的了。下面我们看看 <code>Base</code> 类和 <code>Derived</code> 类的虚表有什么区别。我修改了源码，实例化了一个 <code>Base</code> 类对象 <code>baz</code>，然后分别 dump 出 <code>Base</code> 类和 <code>Derived</code> 类的内存：</p>
<p><img src="/2021/11/10/%E7%90%86%E8%A7%A3C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8/1.5.png" alt="1.5"></p>
<p>可以看出，两个对象的虚表指针是不同的。然后我们看看这两者虚表有什么不同：</p>
<p><img src="/2021/11/10/%E7%90%86%E8%A7%A3C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8/1.6.png" alt="1.6"></p>
<p>这两张虚表的第一个函数不同，因为 <code>Derived</code> 类重写了 <code>vPrint()</code> 方法，所以 <code>Derived</code> 的虚表第一个函数指针会有不同，而 <code>printTag()</code> 我并没有重写，所以两张表指向一个同一个函数。</p>
<p>所以每个类都会维护一张虚表，编译时，编译器根据类的声明创建出虚表，当对象被构造时，虚表的地址就会被写入这个对象内存的起始位置。这就是多态性在 C++ 中实现的方式，而像 Java、OC 这样的语言由于 Runtime 的存在，这些对象会有多余的内存空间记录类的信息（meta-object），在运行时根据这些信息解析出相应的函数去执行。虽然不同，但是异曲同工。</p>
<p>理解虚函数表有什么作用呢？</p>
<ul>
<li>能让你更好地理解 C++</li>
<li>一些 hook 技术就是利用虚表来实现的</li>
</ul>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2021/10/09/C-emplace-back/">C++_emplace_back</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2021-10-09</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</a><i class="fa fa-angle-right" aria-hidden="true"></i><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/C/">C++</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/C/">C++</a></span><div class="content"><p>参考：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/c0cbd9dbf772">https://www.jianshu.com/p/c0cbd9dbf772</a></p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>我们在对STL容器进行插入操作时，常会使用<code>insert</code>或<code>push_back</code>。C++11提出了更高效的插入方法：<code>emplace</code>。</p>
<p>本文将介绍C++11新特性中<code>emplace</code>的使用与原理。</p>
<hr>
<h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>首先，介绍下<code>emplace</code>相对应的函数：</p>
<ul>
<li>对于vector，emplace()可以替换insert()，emplace_back()可以替换push_back()。</li>
<li>对于set，emplace()可以替换insert()。</li>
<li>对于map，emplace()可以替换insert()。</li>
</ul>
<h3 id="简单的使用"><a href="#简单的使用" class="headerlink" title="简单的使用"></a>简单的使用</h3><ul>
<li><p>以vector的emplace_back()为例：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Student</span> &#123;</span></span><br><span class="line">    string name;</span><br><span class="line">    <span class="keyword">int</span> age;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Student</span>(string&amp;&amp; n, <span class="keyword">int</span> a)</span><br><span class="line">        :<span class="built_in">name</span>(std::<span class="built_in">move</span>(n)), <span class="built_in">age</span>(a)</span><br><span class="line">    &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//基本数据类型的插入</span></span><br><span class="line">    vector&lt;<span class="keyword">int</span>&gt; arr;</span><br><span class="line">    arr.<span class="built_in">push_back</span>(<span class="number">1</span>);</span><br><span class="line">    arr.<span class="built_in">emplace_back</span>(<span class="number">1</span>);</span><br><span class="line">  </span><br><span class="line">    <span class="comment">//自定义类型的插入</span></span><br><span class="line">    vector&lt;Student&gt; classes;</span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>); <span class="comment">//无需先创建对象</span></span><br><span class="line">    classes.<span class="built_in">push_back</span>(<span class="built_in">Student</span>(<span class="string">&quot;xiaoming&quot;</span>, <span class="number">23</span>)); <span class="comment">//需先创建对象</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li><p>我们注意到，使用emplace_back向vector增添元素时，无须先创建对象，而使用push_back()向vector中增添元素时，需要先创建对象，再添加到数组中。这就涉及到emplace_back()的工作机制了。</p>
</li>
</ul>
<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><ul>
<li><p><code>push_back()</code>：先向容器尾部添加一个右值元素(临时对象)，然后调用<strong>构造函数</strong>构造出这个临时对象，最后调用<strong>移动构造函数</strong>将这个临时对象放入容器中并释放这个临时对象。<br> <strong>注：最后调用的不是拷贝构造函数，而是移动构造函数。</strong>因为需要释放临时对象，所以通过<code>std::move</code>进行移动构造，可以避免不必要的拷贝操作</p>
</li>
<li><p><code>emplace_back()</code>：在容器尾部添加一个元素，调用<strong>构造函数</strong>原地构造，不需要触发拷贝构造和移动构造。因此比<code>push_back()</code>更加高效。</p>
</li>
<li><p>通过下面程序我们可以验证一下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Student</span> &#123;</span></span><br><span class="line">    string name;</span><br><span class="line">    <span class="keyword">int</span> age;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Student</span>(string&amp;&amp; n, <span class="keyword">int</span> a)</span><br><span class="line">        :<span class="built_in">name</span>(std::<span class="built_in">move</span>(n)), <span class="built_in">age</span>(a)</span><br><span class="line">    &#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;构造&quot;</span> &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Student</span>(<span class="keyword">const</span> Student&amp; s)</span><br><span class="line">        : <span class="built_in">name</span>(std::<span class="built_in">move</span>(s.name)), <span class="built_in">age</span>(s.age)</span><br><span class="line">    &#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;拷贝构造&quot;</span> &lt;&lt; endl;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">Student</span>(Student&amp;&amp; s)</span><br><span class="line">        :<span class="built_in">name</span>(std::<span class="built_in">move</span>(s.name)), <span class="built_in">age</span>(s.age)</span><br><span class="line">    &#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;移动构造&quot;</span> &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Student&amp; <span class="keyword">operator</span>=(<span class="keyword">const</span> Student&amp; s);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    vector&lt;Student&gt; classes_one;</span><br><span class="line">    vector&lt;Student&gt; classes_two;</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;emplace_back:&quot;</span> &lt;&lt; endl;</span><br><span class="line">    classes_one.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line"></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;push_back:&quot;</span> &lt;&lt; endl;</span><br><span class="line">    classes_two.<span class="built_in">push_back</span>(<span class="built_in">Student</span>(<span class="string">&quot;xiaoming&quot;</span>, <span class="number">23</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>执行结果：</p>
</li>
</ul>
<p><img src="/2021/10/09/C-emplace-back/result.jpg" alt="result"></p>
<h2 id="进一步讨论"><a href="#进一步讨论" class="headerlink" title="进一步讨论"></a>进一步讨论</h2><p>看了上文的示例代码，可能会有人问，为什么要分成两个容器分开插入来查看结果呢？因为一起插入时，会触发未知的<strong>拷贝构造</strong>操作，以及多次进行<code>emplace_back()</code>或<code>push_back()</code>操作时，也会触发<strong>拷贝构造</strong>操作。</p>
<p>这些拷贝构造操作是什么呢？这和vector的原理有关。当多次插入操作导致vector已满时，就要分配一块更大的内存(比原始大小多50%)，将原始数据复制过来并释放之前的内存。原始数据的复制就是这些<strong>拷贝构造</strong>操作。</p>
<p>进行多次插入，查看运行结果：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    vector&lt;Student&gt; classes;</span><br><span class="line"></span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line">    classes.<span class="built_in">emplace_back</span>(<span class="string">&quot;xiaohong&quot;</span>, <span class="number">24</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>结果如下，上图打印的“拷贝构造”即vector满时，需要分配更大的内存并复制原始数据而产生的。并且每次分配更大内存时，都比原始大小多50%，即1-&gt;2-&gt;3-&gt;4-&gt;6…</p>
<p><img src="/2021/10/09/C-emplace-back/result2.jpg" alt="result2"></p>
</div><hr></div><div class="recent-post-item article-container"><a class="article-title" href="/2021/09/22/Rust%E7%9A%84Option%E7%9A%84%E6%AD%A3%E7%A1%AE%E6%89%93%E5%BC%80%E6%96%B9%E5%BC%8F/">Rust的Option的正确打开方式</a><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2021-09-22</time><span class="article-meta"><span class="article-meta__separator">|</span><i class="fa fa-inbox article-meta__icon" aria-hidden="true"></i><a class="article-meta__categories" href="/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/">编程语言</a></span><span class="article-meta tags"><span class="article-meta__separator">|</span><i class="fa fa-tag article-meta__icon" aria-hidden="true"></i><a class="article-meta__tags" href="/tags/Rust/">Rust</a></span><div class="content"><p>本文参考：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/ce5bddf4b335">https://www.jianshu.com/p/ce5bddf4b335</a></p>
<hr>
<p>Rust基于强大的类型，泛型系统，采用Option枚举的方式，在程序运行过程中表达“有”和“无”的概念。</p>
<p>回顾一下，Option是一个枚举类型：</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="class"><span class="keyword">enum</span> <span class="title">Option</span></span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="literal">None</span>,</span><br><span class="line">    <span class="literal">Some</span>(T),</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>官方文档请看：<a target="_blank" rel="noopener" href="https://links.jianshu.com/go?to=https://doc.rust-lang.org/std/option/enum.Option.html">std::option::Option</a></p>
<h2 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h2><p>根据<code>std::option</code>的介绍，<code>Option&lt;T&gt;</code>有以下7种用法：</p>
<p>1、初始化值</p>
<p>2、作为在整个输入范围没有定义的函数的返回值</p>
<p>3、如果函数可能有错误，可用 <code>Option&lt;T&gt;</code>作为返回值，用<code>None</code>代表可能出现的错误。</p>
<p>4、用作<code>struct</code>的可选字段</p>
<p>5、用作函数的可选参数</p>
<p>6、空指针</p>
<p>7、用作复杂情况的返回值</p>
<p><strong>初始化值</strong><br>如果一个变量可能有值，也可能没有，那么可以使用Option<T>初始化该变量。</T></p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> s = <span class="literal">Some</span>(<span class="number">3</span>);</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">let</span> <span class="literal">Some</span>(<span class="keyword">ref</span> s) = s&#123;</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;&#123;:?&#125;&quot;</span>,*s);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>有或没有值所造成的影响，只有在使用该变量时才体现出来，否则只是<code>println!()</code>没必要初始化一个<code>Option</code>,能使用一个<code>Option&lt;T&gt;</code>的情况有几种，分别是：</p>
<ul>
<li>作为函数返回值</li>
<li><code>struct</code>有带有<code>Option&lt;T&gt;</code>的字段时，初始化该字段需要创建<code>Option&lt;T&gt;</code></li>
<li>作为函数参数</li>
</ul>
<p><strong>空指针</strong><br>此处搬运标准库的例子<br>Rust的指针指向一定是个合法的内存地址，因此Rust没有空指针，取而代之的是可选指针(optional pointer)，比如<code>Option&lt;Box&lt;T&gt;&gt;</code>。<br> 下面的例子使用<code>Option</code>创建一个<code>Option&lt;Box&lt;i32&gt;&gt;</code>，为了使用i32的值，<code>check_option</code>函数使用了模式匹配，判断box里是否有值，因为Option也可能没值嘛。</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> optional = <span class="literal">None</span>;</span><br><span class="line">check_optional(optional);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> optional = <span class="literal">Some</span>(<span class="built_in">Box</span>::new(<span class="number">9000</span>));</span><br><span class="line">check_optional(optional);</span><br><span class="line"><span class="comment">//接收Option作为参数</span></span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">check_optional</span></span>(optional: <span class="built_in">Option</span>&lt;<span class="built_in">Box</span>&lt;<span class="built_in">i32</span>&gt;&gt;) &#123;</span><br><span class="line">    <span class="keyword">match</span> optional &#123;</span><br><span class="line">        <span class="literal">Some</span>(<span class="keyword">ref</span> p) =&gt; <span class="built_in">println!</span>(<span class="string">&quot;has value &#123;&#125;&quot;</span>, p),</span><br><span class="line">        <span class="literal">None</span> =&gt; <span class="built_in">println!</span>(<span class="string">&quot;has no value&quot;</span>),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的例子使用Option创建一个安全的可空指针(nullable pointer)，这种Option的用法在Rust中很常见，因此Rust对此做了优化，确保<code>Option&lt;Box&lt;T&gt;&gt;</code>像一个很普通的指针那样高效率，且不带额外的开销。</p>
<p><strong>复杂情况的返回值</strong><br> Rust是个静态强类型的语言，特性<code>trait</code>是编译期采用静态分发的方式为每个struct添加方法，因此Rust的抽象没有额外的开销。<br> 当我们调用某个复杂的库，实现其一个带有类型占位符的<code>trait</code>时，我们要指定一个类型，否则编译不过，但是此类型不是随便指定的，它可能需要实现某种<code>trait</code>，因为后续的操作可能要用到某种<code>trait</code>的方法，这时库会给我们提供一些实现某种<code>trait</code>的基础类型，比如<code>Option&lt;T&gt;</code>,<code>Result&lt;T,E&gt;</code>等。</p>
<h2 id="可用操作"><a href="#可用操作" class="headerlink" title="可用操作"></a>可用操作</h2><ol>
<li>match</li>
<li>if let</li>
<li>unwrap()等组合器</li>
</ol>
<h3 id="match"><a href="#match" class="headerlink" title="match"></a>match</h3><p>match是个很强大的模式匹配，很适合匹配Option等枚举类型。</p>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>()&#123;</span><br><span class="line">    <span class="keyword">let</span> d = Student::new(<span class="string">&quot;xiaoming&quot;</span>.to_string(),<span class="number">18</span>);</span><br><span class="line">    <span class="keyword">let</span> d = <span class="literal">Some</span>(d);</span><br><span class="line">   <span class="keyword">match</span> d&#123;</span><br><span class="line">    <span class="literal">Some</span>(s)=&gt;&#123;</span><br><span class="line">      <span class="built_in">println!</span>(<span class="string">&quot;&#123;:?&#125;&quot;</span>, s.name);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="literal">None</span>=&gt;&#123;</span><br><span class="line">      <span class="built_in">println!</span>(<span class="string">&quot;None&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Option的一些基本操作，相信大家一看就懂，这里不再赘述，下面重点看下Option的组合器。这种写法属于函数式编程，刚接触的人肯定对<code>map</code>,   <code>map_or</code>,<code>map_or_else</code>很困惑。<br> 所谓的函数式是一种编程范式，Rust有函数式编程的支持，不代表Rust是函数式编程语言。<br> 想了解函数式编程?<br> <a target="_blank" rel="noopener" href="https://links.jianshu.com/go?to=https://baike.baidu.com/item/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/4035031?fr=aladdin">函数式编程–百度百科</a><br> 下面以说明，源码，例子的顺序，一一介绍这些好玩的东东：<br> <strong>注意：断言<code>assert_eq!(a,b)</code>，如果a==b，什么都不发生，否则中断程序。</strong></p>
<h3 id="expect"><a href="#expect" class="headerlink" title="expect()"></a>expect()</h3><ul>
<li>说明：<br> 有值，返回值，否则，中断程序，打印msg错误信息。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">expect</span></span>(<span class="keyword">self</span>, msg: &amp;<span class="built_in">str</span>) -&gt; T &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(val) =&gt; val,</span><br><span class="line">        <span class="literal">None</span> =&gt; expect_failed(msg),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="string">&quot;value&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.expect(<span class="string">&quot;the world is ending&quot;</span>), <span class="string">&quot;value&quot;</span>);</span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;&amp;<span class="built_in">str</span>&gt; = <span class="literal">None</span>;</span><br><span class="line">x.expect(<span class="string">&quot;the world is ending&quot;</span>); <span class="comment">// panics with `the world is ending`</span></span><br></pre></td></tr></table></figure>

<h3 id="unwrap"><a href="#unwrap" class="headerlink" title="unwrap()"></a>unwrap()</h3><ul>
<li><p>说明：<br>有值，返回值，否则，中断程序。</p>
</li>
<li><p>源码：</p>
</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">unwrap</span></span>(<span class="keyword">self</span>) -&gt; T &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(val) =&gt; val,</span><br><span class="line">        <span class="literal">None</span> =&gt; <span class="built_in">panic!</span>(<span class="string">&quot;called `Option::unwrap()` on a `None` value&quot;</span>),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="string">&quot;air&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.unwrap(), <span class="string">&quot;air&quot;</span>);</span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;&amp;<span class="built_in">str</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.unwrap(), <span class="string">&quot;air&quot;</span>); <span class="comment">// fails</span></span><br></pre></td></tr></table></figure>

<h3 id="unwrap-or"><a href="#unwrap-or" class="headerlink" title="unwrap_or()"></a>unwrap_or()</h3><ul>
<li>说明：<br> 有值，返回值，否则返回一个默认值。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">unwrap_or</span></span>(<span class="keyword">self</span>, def: T) -&gt; T &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(x) =&gt; x,</span><br><span class="line">        <span class="literal">None</span> =&gt; def,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">Some</span>(<span class="string">&quot;car&quot;</span>).unwrap_or(<span class="string">&quot;bike&quot;</span>), <span class="string">&quot;car&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">None</span>.unwrap_or(<span class="string">&quot;bike&quot;</span>), <span class="string">&quot;bike&quot;</span>);</span><br></pre></td></tr></table></figure>

<h3 id="unwrap-or-else"><a href="#unwrap-or-else" class="headerlink" title="unwrap_or_else()"></a>unwrap_or_else()</h3><ul>
<li>说明：<br> 有值，返回值，否则，执行闭包。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">unwrap_or_else</span></span>&lt;F: <span class="built_in">FnOnce</span>() -&gt; T&gt;(<span class="keyword">self</span>, f: F) -&gt; T &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(x) =&gt; x,</span><br><span class="line">        <span class="literal">None</span> =&gt; f(),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> k = <span class="number">10</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">Some</span>(<span class="number">4</span>).unwrap_or_else(|| <span class="number">2</span> * k), <span class="number">4</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">None</span>.unwrap_or_else(|| <span class="number">2</span> * k), <span class="number">20</span>);</span><br></pre></td></tr></table></figure>

<h3 id="map"><a href="#map" class="headerlink" title="map()"></a>map()</h3><ul>
<li>说明：<br> 改变值，并返回另一个Option。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">map</span></span>&lt;U, F: <span class="built_in">FnOnce</span>(T) -&gt; U&gt;(<span class="keyword">self</span>, f: F) -&gt; <span class="built_in">Option</span>&lt;U&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(x) =&gt; <span class="literal">Some</span>(f(x)),</span><br><span class="line">        <span class="literal">None</span> =&gt; <span class="literal">None</span>,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> maybe_some_string = <span class="literal">Some</span>(<span class="built_in">String</span>::from(<span class="string">&quot;Hello, World!&quot;</span>));</span><br><span class="line"><span class="comment">// `Option::map` takes self *by value*, consuming `maybe_some_string`</span></span><br><span class="line"><span class="keyword">let</span> maybe_some_len = maybe_some_string.map(|s| s.len());</span><br><span class="line"></span><br><span class="line"><span class="built_in">assert_eq!</span>(maybe_some_len, <span class="literal">Some</span>(<span class="number">13</span>));</span><br></pre></td></tr></table></figure>

<h3 id="map-or"><a href="#map-or" class="headerlink" title="map_or()"></a>map_or()</h3><ul>
<li>说明：<br> 有值，则执行闭包返回值，否则返回一个自定义的默认值。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">map_or</span></span>&lt;U, F: <span class="built_in">FnOnce</span>(T) -&gt; U&gt;(<span class="keyword">self</span>, default: U, f: F) -&gt; U &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(t) =&gt; f(t),</span><br><span class="line">        <span class="literal">None</span> =&gt; default,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.map_or(<span class="number">42</span>, |v| v.len()), <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;&amp;<span class="built_in">str</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.map_or(<span class="number">42</span>, |v| v.len()), <span class="number">42</span>);</span><br></pre></td></tr></table></figure>

<h3 id="map-or-else"><a href="#map-or-else" class="headerlink" title="map_or_else()"></a>map_or_else()</h3><ul>
<li>说明：<br> 有值，执行闭包，否则执行另一个闭包。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">map_or_else</span></span>&lt;U, D: <span class="built_in">FnOnce</span>() -&gt; U, F: <span class="built_in">FnOnce</span>(T) -&gt; U&gt;(<span class="keyword">self</span>, default: D, f: F) -&gt; U &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(t) =&gt; f(t),</span><br><span class="line">        <span class="literal">None</span> =&gt; default(),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> k = <span class="number">21</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.map_or_else(|| <span class="number">2</span> * k, |v| v.len()), <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;&amp;<span class="built_in">str</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.map_or_else(|| <span class="number">2</span> * k, |v| v.len()), <span class="number">42</span>);</span><br></pre></td></tr></table></figure>

<h3 id="ok-or"><a href="#ok-or" class="headerlink" title="ok_or()"></a>ok_or()</h3><ul>
<li>说明：<br> 有值，返回Result，否则返回自定义的错误。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">ok_or</span></span>&lt;E&gt;(<span class="keyword">self</span>, err: E) -&gt; <span class="built_in">Result</span>&lt;T, E&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(v) =&gt; <span class="literal">Ok</span>(v),</span><br><span class="line">        <span class="literal">None</span> =&gt; <span class="literal">Err</span>(err),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.ok_or(<span class="number">0</span>), <span class="literal">Ok</span>(<span class="string">&quot;foo&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;&amp;<span class="built_in">str</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.ok_or(<span class="number">0</span>), <span class="literal">Err</span>(<span class="number">0</span>));</span><br></pre></td></tr></table></figure>

<h3 id="ok-or-else"><a href="#ok-or-else" class="headerlink" title="ok_or_else()"></a>ok_or_else()</h3><ul>
<li>说明：<br> 有值，返回Result,否则执行代表错误的闭包。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">ok_or_else</span></span>&lt;E, F: <span class="built_in">FnOnce</span>() -&gt; E&gt;(<span class="keyword">self</span>, err: F) -&gt; <span class="built_in">Result</span>&lt;T, E&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(v) =&gt; <span class="literal">Ok</span>(v),</span><br><span class="line">        <span class="literal">None</span> =&gt; <span class="literal">Err</span>(err()),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.ok_or_else(|| <span class="number">0</span>), <span class="literal">Ok</span>(<span class="string">&quot;foo&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;&amp;<span class="built_in">str</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.ok_or_else(|| <span class="number">0</span>), <span class="literal">Err</span>(<span class="number">0</span>));</span><br></pre></td></tr></table></figure>

<h3 id="iter"><a href="#iter" class="headerlink" title="iter()"></a>iter()</h3><ul>
<li>说明：<br> 把Option转换为迭代器。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">iter</span></span>(&amp;<span class="keyword">self</span>) -&gt; Iter&lt;T&gt; &#123;</span><br><span class="line">    Iter &#123; inner: Item &#123; opt: <span class="keyword">self</span>.as_ref() &#125; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="number">4</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.iter().next(), <span class="literal">Some</span>(&amp;<span class="number">4</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;<span class="built_in">u32</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.iter().next(), <span class="literal">None</span>);</span><br></pre></td></tr></table></figure>

<h3 id="and"><a href="#and" class="headerlink" title="and()"></a>and()</h3><ul>
<li>说明：<br> 有值，返回另一Option，否则返回None。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">and</span></span>&lt;U&gt;(<span class="keyword">self</span>, optb: <span class="built_in">Option</span>&lt;U&gt;) -&gt; <span class="built_in">Option</span>&lt;U&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(_) =&gt; optb,</span><br><span class="line">        <span class="literal">None</span> =&gt; <span class="literal">None</span>,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="number">2</span>);</span><br><span class="line"><span class="keyword">let</span> y: <span class="built_in">Option</span>&lt;&amp;<span class="built_in">str</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.and(y), <span class="literal">None</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;<span class="built_in">u32</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="keyword">let</span> y = <span class="literal">Some</span>(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.and(y), <span class="literal">None</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="number">2</span>);</span><br><span class="line"><span class="keyword">let</span> y = <span class="literal">Some</span>(<span class="string">&quot;foo&quot;</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.and(y), <span class="literal">Some</span>(<span class="string">&quot;foo&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;<span class="built_in">u32</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="keyword">let</span> y: <span class="built_in">Option</span>&lt;&amp;<span class="built_in">str</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.and(y), <span class="literal">None</span>);</span><br></pre></td></tr></table></figure>

<h3 id="and-then"><a href="#and-then" class="headerlink" title="and_then()"></a>and_then()</h3><ul>
<li>说明：<br> 有值，执行闭包，否则返回None。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">and_then</span></span>&lt;U, F: <span class="built_in">FnOnce</span>(T) -&gt; <span class="built_in">Option</span>&lt;U&gt;&gt;(<span class="keyword">self</span>, f: F) -&gt; <span class="built_in">Option</span>&lt;U&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(x) =&gt; f(x),</span><br><span class="line">        <span class="literal">None</span> =&gt; <span class="literal">None</span>,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">sq</span></span>(x: <span class="built_in">u32</span>) -&gt; <span class="built_in">Option</span>&lt;<span class="built_in">u32</span>&gt; &#123; <span class="literal">Some</span>(x * x) &#125;</span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">nope</span></span>(_: <span class="built_in">u32</span>) -&gt; <span class="built_in">Option</span>&lt;<span class="built_in">u32</span>&gt; &#123; <span class="literal">None</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">Some</span>(<span class="number">2</span>).and_then(sq).and_then(sq), <span class="literal">Some</span>(<span class="number">16</span>));</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">Some</span>(<span class="number">2</span>).and_then(sq).and_then(nope), <span class="literal">None</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">Some</span>(<span class="number">2</span>).and_then(nope).and_then(sq), <span class="literal">None</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">None</span>.and_then(sq).and_then(sq), <span class="literal">None</span>);</span><br></pre></td></tr></table></figure>

<h3 id="filter"><a href="#filter" class="headerlink" title="filter()"></a>filter()</h3><ul>
<li>说明：<br> 过滤器，过滤出自己想要的值。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">filter</span></span>&lt;P: <span class="built_in">FnOnce</span>(&amp;T) -&gt; <span class="built_in">bool</span>&gt;(<span class="keyword">self</span>, predicate: P) -&gt; <span class="keyword">Self</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">let</span> <span class="literal">Some</span>(x) = <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> predicate(&amp;x) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">Some</span>(x)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="literal">None</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">is_even</span></span>(n: &amp;<span class="built_in">i32</span>) -&gt; <span class="built_in">bool</span> &#123;</span><br><span class="line">    n % <span class="number">2</span> == <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">None</span>.filter(is_even), <span class="literal">None</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">Some</span>(<span class="number">3</span>).filter(is_even), <span class="literal">None</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">Some</span>(<span class="number">4</span>).filter(is_even), <span class="literal">Some</span>(<span class="number">4</span>));</span><br></pre></td></tr></table></figure>

<h3 id="or"><a href="#or" class="headerlink" title="or()"></a>or()</h3><ul>
<li>说明：<br> 有值，返回自身，否则返回自定义的Option。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">or</span></span>(<span class="keyword">self</span>, optb: <span class="built_in">Option</span>&lt;T&gt;) -&gt; <span class="built_in">Option</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(_) =&gt; <span class="keyword">self</span>,</span><br><span class="line">        <span class="literal">None</span> =&gt; optb,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="number">2</span>);</span><br><span class="line"><span class="keyword">let</span> y = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.or(y), <span class="literal">Some</span>(<span class="number">2</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x = <span class="literal">None</span>;</span><br><span class="line"><span class="keyword">let</span> y = <span class="literal">Some</span>(<span class="number">100</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.or(y), <span class="literal">Some</span>(<span class="number">100</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x = <span class="literal">Some</span>(<span class="number">2</span>);</span><br><span class="line"><span class="keyword">let</span> y = <span class="literal">Some</span>(<span class="number">100</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.or(y), <span class="literal">Some</span>(<span class="number">2</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: <span class="built_in">Option</span>&lt;<span class="built_in">u32</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="keyword">let</span> y = <span class="literal">None</span>;</span><br><span class="line"><span class="built_in">assert_eq!</span>(x.or(y), <span class="literal">None</span>);</span><br></pre></td></tr></table></figure>

<h3 id="or-else"><a href="#or-else" class="headerlink" title="or_else()"></a>or_else()</h3><ul>
<li>说明：<br> 有值，返回自身，否则执行闭包。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">or_else</span></span>&lt;F: <span class="built_in">FnOnce</span>() -&gt; <span class="built_in">Option</span>&lt;T&gt;&gt;(<span class="keyword">self</span>, f: F) -&gt; <span class="built_in">Option</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">        <span class="literal">Some</span>(_) =&gt; <span class="keyword">self</span>,</span><br><span class="line">        <span class="literal">None</span> =&gt; f(),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>栗子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">nobody</span></span>() -&gt; <span class="built_in">Option</span>&lt;&amp;<span class="symbol">&#x27;static</span> <span class="built_in">str</span>&gt; &#123; <span class="literal">None</span> &#125;</span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">vikings</span></span>() -&gt; <span class="built_in">Option</span>&lt;&amp;<span class="symbol">&#x27;static</span> <span class="built_in">str</span>&gt; &#123; <span class="literal">Some</span>(<span class="string">&quot;vikings&quot;</span>) &#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">Some</span>(<span class="string">&quot;barbarians&quot;</span>).or_else(vikings), <span class="literal">Some</span>(<span class="string">&quot;barbarians&quot;</span>));</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">None</span>.or_else(vikings), <span class="literal">Some</span>(<span class="string">&quot;vikings&quot;</span>));</span><br><span class="line"><span class="built_in">assert_eq!</span>(<span class="literal">None</span>.or_else(nobody), <span class="literal">None</span>);</span><br></pre></td></tr></table></figure>

<h3 id="take"><a href="#take" class="headerlink" title="take()"></a>take()</h3><ul>
<li>说明：<br> 取出一个值。</li>
<li>源码：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">take</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) -&gt; <span class="built_in">Option</span>&lt;T&gt; &#123;</span><br><span class="line">    mem::replace(<span class="keyword">self</span>, <span class="literal">None</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>例子：</li>
</ul>
<figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut</span> x = <span class="literal">Some</span>(<span class="number">2</span>);</span><br><span class="line"><span class="keyword">let</span> y = x.take();</span><br><span class="line"><span class="built_in">assert_eq!</span>(x, <span class="literal">None</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(y, <span class="literal">Some</span>(<span class="number">2</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="keyword">mut</span> x: <span class="built_in">Option</span>&lt;<span class="built_in">u32</span>&gt; = <span class="literal">None</span>;</span><br><span class="line"><span class="keyword">let</span> y = x.take();</span><br><span class="line"><span class="built_in">assert_eq!</span>(x, <span class="literal">None</span>);</span><br><span class="line"><span class="built_in">assert_eq!</span>(y, <span class="literal">None</span>);</span><br></pre></td></tr></table></figure>

</div><hr></div><nav id="pagination"><div class="pagination"><span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><a class="extend next" rel="next" href="/page/2/"><i class="fa fa-chevron-right"></i></a></div></nav></div></div><footer><div class="layout" id="footer"><div class="copyright">&copy;2013 - 2022 By LiYang</div><div class="framework-info"><span>Driven - </span><a target="_blank" rel="noopener" href="http://hexo.io"><span>Hexo</span></a><span class="footer-separator">|</span><span>Theme - </span><a target="_blank" rel="noopener" href="https://github.com/Molunerfinn/hexo-theme-melody"><span>Melody</span></a></div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_site_uv"><i class="fa fa-user"></i><span id="busuanzi_value_site_uv"></span><span></span></span><span class="footer-separator">|</span><span id="busuanzi_container_site_pv"><i class="fa fa-eye"></i><span id="busuanzi_value_site_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/js/utils.js?version=1.9.0"></script><script src="/js/fancybox.js?version=1.9.0"></script><script src="/js/sidebar.js?version=1.9.0"></script><script src="/js/copy.js?version=1.9.0"></script><script src="/js/fireworks.js?version=1.9.0"></script><script src="/js/transition.js?version=1.9.0"></script><script src="/js/scroll.js?version=1.9.0"></script><script src="/js/head.js?version=1.9.0"></script><script>if(/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
  $('#top-container').addClass('is-mobile')
}</script></body></html>